PowerShell: I need to understand why parameters are interpreted as NULL - powershell

I'm getting an error that I can't call a method on a null valued expression. However, I'm not sure WHY the parameters are resulting in a null value. I need a second set of eyes to look at this and give me some guidance.
$docpath = "c:\users\x\desktop\do"
$htmPath = "c:\users\x\desktop\ht"
$txtPath = "c:\users\x\desktop\tx"
$srcPath = "c:\users\x\desktop\ht"
#
$srcfilesTXT = Get-ChildItem $txtPath -filter "*.htm*"
$srcfilesDOC = Get-ChildItem $docPath -filter "*.htm*"
$srcfilesHTM = Get-ChildItem $htmPath -filter "*.htm*"
#
function rename-documents ($docs) {
Move-Item -txtPath $_.FullName $_.Name.Replace("\.htm", ".txt")
Move-Item -docpath $_.FullName $_.Name.Replace("\.htm", ".doc")
}
ForEach ($doc in $srcpath) {
Write-Host "Renaming :" $doc.FullName
rename-documents -docs $doc.FullName
$doc = $null
}
And the error....
You cannot call a method on a null-valued expression.
At C:\users\x\desktop\foo002.ps1:62 char:51
+ Move-Item -txtPath $_.FullName $_.FullName.Replace <<<< ("\.htm", ".txt")
+ CategoryInfo : InvalidOperation: (Replace:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At C:\users\x46332\desktop\foo002.ps1:63 char:51
+ Move-Item -docpath $_.FullName $_.FullName.Replace <<<< ("\.htm", ".doc")
+ CategoryInfo : InvalidOperation: (Replace:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
First: it appears that my ("\.htm", ".txt") is what's showing up as null. I've tried it without the \ - (".htm", ".txt") - as well and received the same results.
Second: syntactically, I'm interpreting my line as move-item <path> <source-file-passed-to-function> <replacement=name-for-file> (parameters-for-replacement). Is that an appropriate understanding of what this code is doing?
Third: Do I need to have a -literalpath parameter in there somewhere? MS TechNet and get-help have very little information on the uses of the -literalpath parameter; I was unable to find something relevant to my particular situation.
Help me understand what I'm missing. Thanks!

In the context of a simple function $_ is not defined. $_ is only valid in a pipeline. That is, $_ reprensents the current object being passed down the pipeline.
With your current function definition try it this way:
function Rename-HtmlDocument([System.IO.FileInfo]$docs, $newExt) {
$docs | Move-Item -Dest {$_.FullName -replace '\.htm$', $newExt}
}
You can pass this function the $srcfilesDOC and $srcFilesTXT variables directly e.g.:
Rename-HtmlDocument $srcFilesDOC .doc
Rename-HtmlDocument $srcFilesTXT .txt
Of course you could make this more generic and get the source extension from the FileInfo object e.g.:
function Rename-DocumentExtension([System.IO.FileInfo]$docs, $newExt) {
$docs | Move-Item -Dest {$_.FullName.Replace($_.Extension, $newExt)}
}
BTW PowerShell's Move-Item command doesn't have the parameters you're using -txtPath and -docPath. Is this a function you've created?

Related

Push-Location not working for certain paths [duplicate]

I modified PowerShell script from PowerShell - Batch change files encoding To UTF-8.
# Modified version of https://stackoverflow.com/q/18684793
[Threading.Thread]::CurrentThread.CurrentUICulture = 'en-US'
$Encoding = New-Object System.Text.UTF8Encoding($True) # If UTF8Encoding($False), It will be UTF-8 without BOM
$source = "C:\Users\AKULA\Desktop\SRC" # source directory
$destination = "C:\Users\AKULA\Desktop\DST" # destination directory
if (!(Test-Path $destination)) {
New-Item -Path $destination -ItemType Directory | Out-Null
}
# Delete all previously generated file
Get-ChildItem -Path $destination -Include * -File -Recurse | ForEach-Object {$_.Delete()}
# Recursively convert all files into UTF-8
foreach ($i in Get-ChildItem $source -Force -Recurse -Exclude "desktop.ini") {
if ($i.PSIsContainer) {
continue
}
$name = $i.Fullname.Replace($source, $destination)
$content = Get-Content $i.Fullname
if ($null -ne $content) {
[System.IO.File]::WriteAllLines($name, $content, $Encoding)
} else {
Write-Host "No content from: $i"
}
}
But after using it, I've found that PS cannot handle [ or ] well.
I made some test files that has diversity in name/content.
Get-Content : An object at the specified path C:\Users\AKULA\Desktop\SRC\FILENAME[[[[[[]]]]]]]].txt does not exist, or
has been filtered by the -Include or -Exclude parameter.
At C:\Users\AKULA\Desktop\Convert_to_UTF-8.ps1:24 char:16
+ $content = Get-Content $i.Fullname
+ ~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (System.String[]:String[]) [Get-Content], Exception
+ FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetContentCommand
Since I cannot embed images in question, here is link of IMGUR album.
Full image list: https://imgur.com/a/aN1RG2L
These are what I've tested:
Test files have different names. Their name contains space, ',
[]. Also made up different language(Japanese, Korean).
These files have same content, encoded with UCS-2 BE BOM(UTF-16 BE) so
that I can check if it has re-encoded to UTF-8.
How can I make my script handle [ or ] in file name well?
tl;dr
Indeed, use of the -LiteralPath parameter is the best solution (in PowerShell (Core) v6+, you can shorten to -lp):
$content = Get-Content -LiteralPath $i.Fullname
-LiteralPath ensures that $i.Fullname is taken verbatim (literally); that is, [ and ] in the path are interpreted as themselves rather than having special meaning, as they would have as a -Path argument, due to being interpreted as a wildcard expression - note that -Path is positionally implied if you only pass a value (a string) as the first argument, as you did (Get-Content $i.FullName)
Note: This answer analogously applies to all cmdlets that have both -Path and -LiteralPath parameters, such as Set-Content, Out-File, and Set-Location.
As for what you tried:
$content = Get-Content $i.Fullname
is effectively the same as:
$content = Get-Content -Path $i.Fullname
That is, the (first) positional argument passed to Get-Content is implicitly bound to the
-Path parameter.
The -Path parameter accepts wildcard expressions to allow matching paths by patterns; in addition to support for * (any run of characters) and ? (exactly 1 character), [...] inside a wildcard pattern denotes a character set or range (e.g., [12] or [0-9]).
Therefore an actual path that contains [...], e.g., foo[10].txt, is not recognized as such, because the [10] is interpreted as a character set matching a single character that is either 1 or 0; that is foo[10].txt would match foo0.txt and foo1.txt, but not a file literally named foo[10].txt.
When (implicitly) using -Path, it is possible to escape [ and ] instances that should be interpreted verbatim, namely via the backtick (`), but note that this can get tricky to get right when quoting and/or variable references are involved.
If you know a path to be a literal path, it is best to form a habit of using -LiteralPath (which in PowerShell Core you can shorten to -lp).
However, if your path contains literal [ and ] and you also need wildcard matching, you must use `-escaping - see this answer.
There are at least two situations where the solution's good advice doesn't hold, unfortunately.
Selective error handling
Get-Content -LiteralPath "nobox[]" gives an error message and exception type as if wildcards are involved:
Get-Content : An object at the specified path box[] does not exist, or has been filtered by the -Include or -Exclude parameter.
At line:1 char:1
+ Get-Content -Path "nobox[]"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (System.String[]:String[]) [Get-Content], Exception
+ FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetContentCommand
whereas without the brackets, we get:
Get-Content : Cannot find path 'nobox' because it does not exist.
At line:1 char:1
+ Get-Content -LiteralPath "nobox"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (nobox:String) [Get-Content], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
Therefore, to silently deal with an optional file, something like:
try {
$lines = Get-Content -LiteralPath $path -ErrorAction Stop
}
catch [System.Management.Automation.ItemNotFoundException] {
$lines = #()
}
chokes on paths with brackets.
Creating a hard or symbolic link
A minor and a major caveat:
The Path parameter, the name of the new item, "works like the LiteralPath parameter of other cmdlets", says the documentation of New-Item clearly, and that seems true and makes sense. Though I wish we could clarify that by writing -LiteralPath.
The Value parameter, the target of the link (also known as Target secretly in v5 and openly later), does not accept wildcard characters according to the same documentation, but that's a lie. The command:
New-Item -ItemType "HardLink" -Path "whatever" -Target "*"
makes Powershell squeal "Cannot set the location because path '*' resolved to multiple containers.".
So you always need the escapes for the target. If you have a file named "f[]", then this will display an error:
New-Item -ItemType "HardLink" -Path "whatever" -Target "f[]"
and this will create a link:
New-Item -ItemType "HardLink" -Path "f[2]" -Target ([WildcardPattern]::Escape("f[]"))
Same for ItemType "SymbolicLink".

Feeding Get-ChildItem path info from an array - Illegal characters

I need to iterate over an array of folder names, passing them into GCI so I can run operations on their contents. No matter what I do, it keeps giving me this error:
gci : Illegal characters in path.
At C:\Scripts\Arvest_submission_monitoring.ps1:86 char:5
+ gci -path "D:\subftp\$_" -recurse {
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (D:\SUBFTP\folder1:String [Get-ChildItem], ArgumentException
+ FullyQualifiedErrorId :
DirArgumentError,Microsoft.PowerShell.Commands.GetChildItemCommand
I've created an array:
$folders = "folder1","folder2","folder3"
And then iterate over it:
$folders | % {
gci -Path "D:\subftp\$_" -Recurse {
#do stuff here
}
}
I've tried many various options such as a regex replace to remove any possible illegal characters (although a $folder.Count shows me there are no invisible characters), I've tried turning $folders into a C# ArrayList but it's still a no go. I've even played around with the path variable itself concatenating it into a single string before use.
The weird thing is if I try and execute it from the command line it works fine. What gives? I'm running v5.
It is caused by the curly brace right at the end of your command gci -Path "D:\subftp\$_" -Recurse {.
Probably you wanted to do this:
$folders | ForEach-Object {
Get-ChildItem -Path "D:\subftp\$_" -Recurse | ForEach-Object {
#do stuff here
}
}

Issue with foreach + get-childitem

I'm doing a script to organize my media. I download files in one directory to accomodate them before adding to my media center.
If for example I have a file called Breaking.Bad.S01E01.DVDRip.XviD-ORPHEUS.avi I would like the script to get the name of the show, check for season on S01 and move that file to a folder in another disk, for example e:\series\breaking bad\season 01
So far it checks if the file is call s01e01 or S01E01 or s01.e01 or S01.E01 and returns Breaking Bad\Season 01, creates the path to move to and the moving action itself
I have part of that script but I cannot make get-childitem to work with foreach.
This is what I have so far and the error I'm getting:
CODE
$FilesList = Get-ChildItem -name -recurse -include *.mkv,*.mp4,*.srt,*.avi
$FilesList
foreach ($FL_Item in $FilesList)
{
$SeriesName = ($FL_Item.BaseName -split '\.s\d')[0].Replace('.', ' ')
$SE_Info = $FL_Item.BaseName.Split('.')[-3] -split 'e'`
$Season = $SE_Info[0] -replace 's', 'Season '
#$Episode = 'Episode{0}' -f $SE_Info[1]
$SeriesName
$Season
#$Episode
$SeriesDirectory = Join-Path -Path "$SeriesName" -ChildPath "$Season"
$SeriesDirectory
#$MoverArchivo = move-item -path $FileName -destination e:\series\$SeriesDirectory
#$MoverArchivo
''
}
OUTPUT I'm getting
Breaking.Bad.S01E01.DVDRip.XviD-ORPHEUS.avi
Breaking.Bad.S01E01.DVDRip.XviD-ORPHEUS.spa.srt
Breaking.Bad.S04E01.Box.Cutter.720p.hdtv.x264-orenji.mkv
Breaking.Bad.S04E01.Box.Cutter.720p.hdtv.x264-orenji.spa.srt
Breaking.Bad.S05E15.720p.HDTV.x264-EVOLVE.mkv
Breaking.Bad.S05E15.720p.HDTV.x264-EVOLVE.spa.srt
Path Of Blood (2018) [WEBRip] [1080p] [YTS.AM]\Path.Of.Blood.2018.1080p.WEBRip.x264-[YTS.AM].mp4
They Shall Not Grow Old (2018) [BluRay] [1080p] [YTS.AM]\They.Shall.Not.Grow.Old.2018.1080p.BluRay.x264-[YTS.AM].mp4
ERROR
You cannot call a method on a null-valued expression.
At D:\shared\temp\test3.ps1:8 char:5
+ $SE_Info = $FL_Item.BaseName.Split('.')[-3] -split 'e'
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Cannot index into a null array.
At D:\shared\temp\test3.ps1:10 char:5
+ $Season = $SE_Info[0] -replace 's', 'Season '
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : NullArray
Join-Path : Cannot bind argument to parameter 'Path' because it is an empty string.
At D:\shared\temp\test3.ps1:17 char:37
+ $SeriesDirectory = Join-Path -Path "$SeriesName" -ChildPath "$Sea ...
+ ~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Join-Path], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,Microsoft.PowerShell.Commands.Join
PathCommand
Any ideas what could be wrong?
You are using the -name parameter of Get-ChildItem in your first line:
$FilesList = Get-ChildItem -name -recurse -include *.mkv,*.mp4,*.srt,*.avi
which means it will return just the names of the files as strings.
Later, in your loop, you access each element using the BaseName property, which is a property of FileInfo objects, not strings. So, $FL_Item.BaseName returns an empty string and you get the errors as shown.
Just remove the -name and it should work (or at least you won't get those errors).
I'd use a RegEx with (named) capture groups to grep the Series,Season and Episode number.
See the RegEx working live on regex101.com
## Q:\Test\2018\12\20\SO_53875674.ps1
$DstBase = "E:\series"
Get-ChildItem -Include *.mkv,*.mp4,*.srt,*.avi -Recurse -File|
Where-Object BaseName -match "^(?<Series>.*?)\.?S(?<Season>\d{1,2})\.?E(?<Episode>\d{2})"|
ForEach-Object {
$Destination = "{0}\{1}\Season {2:00}\" -f $DstBase,$Matches.Series.replace('.',' ').Trim(),[int]$Matches.Season
if (!(Test-Path $Destination)){MD $Destination -Force | Out-Null}
"Moving file [{0}] to [{1}]" -f $_.FullName,$Destination
$_ | Move-Item -Destination $Destination -Force
}
Sample tree (with above data) after running the script:
> tree /F
└───series
└───Breaking Bad
├───Season 01
│ Breaking.Bad.S01E01.DVDRip.XviD-ORPHEUS.avi
│ Breaking.Bad.S01E01.DVDRip.XviD-ORPHEUS.spa.srt
│
├───Season 04
│ Breaking.Bad.S04E01.Box.Cutter.720p.hdtv.x264-orenji.mkv
│ Breaking.Bad.S04E01.Box.Cutter.720p.hdtv.x264-orenji.spa.srt
│
└───Season 05
Breaking.Bad.S05E15.720p.HDTV.x264-EVOLVE.mkv
Breaking.Bad.S05E15.720p.HDTV.x264-EVOLVE.spa.srt

Rename a list of files

I need to rename all files below pwd named all_v4_0.csv to all_v4_1.csv.
So far, I have worked my way to this piece of PowerShell:
$oldfiles = dir -recurse | ?{$_.Name -eq "all_v4_0.csv"}
foreach ($o in $oldfiles) {
$o.CopyTo Join-Path $o.Directory.ToString() "all_v4_1.csv"
}
But the foreach loop fails with the message that
At line:2 char:15
+ $o.CopyTo Join-Path $o.Directory.ToString() "all_v4_1.csv"
+ ~~~~~~~~~
Unexpected token 'Join-Path' in expression or statement.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : UnexpectedToken
What am I doing wrong here?
Update, 20150604
As commented below by Manuel Batsching, the original version can be fixed by adding two layers of parentheses: one to indicate function argument, and one to force evaluation order:
$oldfiles = dir -recurse | ?{$_.Name -eq "all_v4_0.csv"}
foreach ($o in $oldfiles) {
$o.CopyTo((Join-Path $o.Directory.ToString() "all_v4_1.csv"))
}
For the problem at hand, one of the solutions with .FullName.Replace would probably be easier.
PSH's parser is not reading the Join-Path and its arguments as an expression to evaluate and pass result to outer expression. So parentheses to force evaluation order.
But additionally CopyTo is a .NET member, rather than a PSH cmdlet, so it needs to have parentheses around its argument (and no space).
Thus:
$o.CopyTo((Join-Path $o.Directory.ToString() "all_v4_1.csv"))
(Possibly using PSH's Copy-Item cmdlet would be a cleaner option.)
Set the target name separately before doing the copy. The below should work:
$oldfiles = dir -recurse | ?{$_.Name -eq "all_v4_0.csv"}
foreach ($o in $oldfiles) {
$newName = $o.FullName.replace("all_v4_0.csv","all_v4_1.csv")
Copy-Item $o.FullName $newName
}
If you want to keep things simple you can also use string concatenation to create the target path.
Get-ChildItem -Recurse -Include 'all_v4_0.csv' |
ForEach { $_.MoveTo($_.Directory.FullName + '\all_v4_1.csv') }
Simply replace the substring in the new name of the files:
Get-ChildItem -Recurse -Include 'all_v4_0.csv' |
Rename-Item -NewName { $_.Name.Replace('4_0', '4_1') }

Powershell WMI Query failing when executed from Task Scheduler

i have a strange problem...
i have the following code, which takes the output from Sysinternals Disk Usage tool (link below)
Disk Usage - Sys Internals
so first i get the physical drives into array $Disks, then i enumerate these through the foreach and mess about with them.
my problem lies in this line $Dir = du.exe -q -v $d.DeviceID
$PC = get-content env:COMPUTERNAME
$Disk = gwmi win32_logicaldisk -filter "drivetype=3"
foreach ($d in $Disk)
{
$Dir = du.exe -q -v $d.DeviceID
$Dir[8..($Dir.length-8)] | foreach {
$Size = $_.substring(0,10).replace(",","")/1024
$Path = $_.substring(10)
}
}
$d.DeviceID should be the drive letter (i.e. C:)
then i populate $Dir with the output from DU.exe, but $d.DeviceID is not acting how it is supposed to, running this from a task has this following result (added a line that says $d.DeviceID, to show the output):
B:
Cannot index into a null array.
At C:\DU.ps1:25 char:6
+ $Dir[ <<<< 8..($Dir.length-8)] | foreach {
+ CategoryInfo : InvalidOperation: (System.Object[]:Object[]) [],
RuntimeException
+ FullyQualifiedErrorId : NullArray
C:
Cannot index into a null array.
At C:\DU.ps1:25 char:6
+ $Dir[ <<<< 8..($Dir.length-8)] | foreach {
+ CategoryInfo : InvalidOperation: (System.Object[]:Object[]) [],
RuntimeException
+ FullyQualifiedErrorId : NullArray
D:
Cannot index into a null array.
At C:\DU.ps1:25 char:6
+ $Dir[ <<<< 8..($Dir.length-8)] | foreach {
+ CategoryInfo : InvalidOperation: (System.Object[]:Object[]) [],
RuntimeException
+ FullyQualifiedErrorId : NullArray
running it from the ISE or just from the Shell has no issues, running it on other servers from all methods works.
i do believe the population of the $Dir vairable is the problem, as the du.exe has trouble with the $d.DeviceID
i dont understand why it is just this server/task sheduler that has the issue. i have tried the following:
redefined the array element to $i = $d.deviceID to fix it down - nothing
exported the job from other server (both DCs) an imported - nothing
restarted the winmgmt service - nothing
i think its a permissions issue, but im running this on an AD as THE Dom Admin with top privilages.
please can you guys help on this one, really am stuck...
cheers
Lee
Yet another update based on comment below:
Try:
$cmd = "du.exe `-q `-v $($d.DeviceID)"
$dir = Invoke-Expression $cmd
Updating as per the comment below.
Take this example. This can get the size of every folder and display size and full path to the folder.
Function Get-FolderSize {
Param ($folderPath)
$colItems = (Get-ChildItem $folderPath -recurse | Measure-Object -property length -sum)
return $colItems.sum/1MB
}
$folders = Get-ChildItem -Recurse C:\Scripts
$folders | % {
if ($_.PSIsContainer) {
$size = Get-FolderSize $_.FullName
Write-Host $size
Write-Host $_.FullName
}
}
You can use WMI to get the drive letter and pass it to the script. For example:
$disks = gwmi win32_logicaldisk -filter "drivetype=3"
$disks | % {
$items = Get-ChildItem -Recurse $_.DeviceID -Force
$items | % {
if ($_.PSIsContainer) {
$size = Get-FolderSize $_.FullName
Write-Host $size
Write-Host $_.FullName
}
}
}
So, with this, you dont need DU.exe. You can run this as a script.
--------------OLD ANSWER _-------------------
First thing I would suspect is the path to DU.exe. What is the working directory set on the scheduled task? Is it the place where DU.exe is available?
BTW, what is the goal of this script? Are you just looking at the disk drive size? What are you capturing into $path? I did not have the patience to test your code. But, I feel that this can be easily achieved using just WMI and no other external tools.