Issue with foreach + get-childitem - powershell

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

Related

Bulk renaming files to add sequential prefixes via PowerShell

I have two folders all with multiple .msg files I need to add a numerical, sequential prefix to. One folder has over 1500 individual files.
I want the end result to go from this:
"[EXTERNAL] RE%3A Auth....msg"
"Fees....msg"
"Hello There...msg"
To this:
"1[EXTERNAL] RE%3A Auth....msg"
"2Fees....msg"
"3Hello There...msg"
I have tried to combine a few results from other similar threads I have found here and have come up with the following:
$i = 1
#Get-ChildItem *.msg | %{Rename-Item $_ -NewName ("{0:000#}_$($_.directory.name).msg" -f $i++)}
When I run this script, I get the following errors:
At C:\users\me\Documents\PrependNumbers.ps1:10 char:5
+ #Get-ChildItem *.msg | %{Rename-Item $_ -NewName ("{0:000#}_$($_.dire ...
+ ~~~~~~~~~~
Unexpected token '-ChildItem' in expression or statement.
At C:\users\me\Documents\PrependNumbers.ps1:10 char:16
+ #Get-ChildItem *.msg | %{Rename-Item $_ -NewName ("{0:000#}_$($_.dire ...
+ ~~~~~
Unexpected token '*.msg' in expression or statement.
At C:\users\me\Documents\PrependNumbers.ps1:10 char:1
+ #Get-ChildItem *.msg | %{Rename-Item $_ -NewName ("{0:000#}_$($_.dire ...
+ ~~~~
The splatting operator '#' cannot be used to reference variables in an expression. '#Get' can be used only as an
argument to a command. To reference variables in an expression use '$Get'.
+ CategoryInfo : ParserError: (:) [], ParseException
+ FullyQualifiedErrorId : UnexpectedToken
I feel like I probably just have a syntax error somewhere, but am unable to tell where exactly from these errors.

Looping Through Files to Run PowerShell Script

I am trying to loop through an unknown number of Excel files in a folder and rename the 1st worksheet in each file to a specific name.
What I have so far is:
Clear-Host
$file = Get-ChildItem -Path "C:\PowerShell\BA" -Name -Include *.xlsx
Write-Output $file
This does list out all of the Excel files in the folder. I am then trying to run one of the commands from the ImportExcel module to rename the first worksheet to "Sheet1".
foreach ($i in $file )
{
$xl= Open-ExcelPackage $file
$sheet1 = $xl.Workbook.Worksheets[1]
$sheet1.Name ="Sheet1"
Close-ExcelPackage $xl
}
But when I run this code, I get the following error for each of the files in the folder:
WARNING: Could not find C:\WINDOWS\system32\11.25.2020_JH_BDX.xlsx 11.25.2020_JH_COV.xlsx 11.25.2020_JH_MISC.xlsx bx_1_coverage_report_2020-11-25 Final V.1 .xlsx bx_2_misc_report_2020-11-25 Final V.1 .xlsx bx_3_bordereau_report_2020-11-25 Final. V.1 .xlsx ic
at_cov_20201126053019.xlsx icat_misc_20201126053024.xlsx
Cannot index into a null array.
At line:8 char:1
+ $sheet1 = $xl.Workbook.Worksheets[1]
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : NullArray
Close-ExcelPackage : Cannot bind argument to parameter 'ExcelPackage' because it is null.
At line:10 char:20
+ Close-ExcelPackage $xl
+ ~~~
+ CategoryInfo : InvalidData: (:) [Close-ExcelPackage], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Close-ExcelPackage
If I run in 32-bit instead of 64-bit, it looks like it is looking for the files here:
WARNING: Could not find C:\Users\1192643\11.25.2020_JH_BDX.xlsx 11.25.2020_JH_COV.xlsx 11.25.2020_JH_MISC.xlsx bx_1_coverage_report_2020-11-25 Final V.1 .xlsx bx_2_misc_report_2020-11-25 Final V.1 .xlsx bx_3_bordereau_report_2020-11-25 Final. V.1 .xlsx icat_
cov_20201126053019.xlsx icat_misc_20201126053024.xlsx
I'm not sure why it is looking in C:\WINDOWS\system32 or my User director for the Excel files. I have tried to ensure it looks in the correct folder by adding the full path in the foreach block with the following:
foreach ($i in $file )
{
$xl= Open-ExcelPackage "C:\PowerShell\BA\"$file
$sheet1 = $xl.Workbook.Worksheets[1]
$sheet1.Name ="Sheet1"
Close-ExcelPackage $xl
}
But that does not work either. Could anyone help me understand what I am missing here? I am on version 5.1.17763.1490.
Updating the $xl variable to $xl= Open-ExcelPackage $i.fullname gives the following errors - so it seems to have the right path now, but it doesn't like the code.
Open-ExcelPackage : Cannot bind argument to parameter 'Path' because it is null.
At C:\PowerShell\BA\RenameWorksheet.ps1:23 char:24
+ $xl= Open-ExcelPackage $i.fullname
+ ~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Open-ExcelPackage], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Open-ExcelPackage
Cannot index into a null array.
At C:\PowerShell\BA\RenameWorksheet.ps1:24 char:1
+ $sheet1 = $xl.Workbook.Worksheets[1]
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : NullArray
The property 'Name' cannot be found on this object. Verify that the property exists and can be set.
At C:\PowerShell\BA\RenameWorksheet.ps1:25 char:1
+ $sheet1.Name ="Sheet1"
+ ~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : PropertyNotFound
Close-ExcelPackage : Cannot bind argument to parameter 'ExcelPackage' because it is null.
At C:\PowerShell\BA\RenameWorksheet.ps1:26 char:20
+ Close-ExcelPackage $xl
+ ~~~
+ CategoryInfo : InvalidData: (:) [Close-ExcelPackage], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Close-ExcelPackage
To test the code, I was able to update the worksheet name in a specific .xlsx file by using:
$xl= Open-ExcelPackage "C:\PowerShell\BA\11.25.2020_JH_MISC.xlsx"
$sheet1 = $xl.Workbook.Worksheets[1]
$sheet1.Name ="Sheet1"
Close-ExcelPackage $xl
Thanks,
Don't reference $file inside the loop. If you are looking for a handle on one of the files, use $i, because that's the name you chose in the setup of your foreach. Note that I have put the $i inside the quoted string.
foreach ($i in $file )
{
$xl= Open-ExcelPackage "C:\PowerShell\BA\$i"
$sheet1 = $xl.Workbook.Worksheets[1]
$sheet1.Name ="Sheet1"
Close-ExcelPackage $xl
}
When working with PowerShell, there are two things to remember.
PowerShell is object-oriented.
PowerShell uses pipelines.
Here we have a variable and a loop. Both can be eliminated by switching to a pipeline.
Take care to read the help file for Get-ChildItem.
As Doug mentioned, the -Name parameter:
-Name
Gets only the names of the items in the location. The output is a string object that can be sent down the pipeline to other commands. Wildcards are permitted.
Here we go from a FileInfo object to a String. So we lose valuable information like the location of each file: $_.FullName This contributed to the issue with $i.fullname
Also, -Include requires a special wildcard in the -Path.
-Include
Specifies, as a string array, an item or items that this cmdlet includes in the operation. The value of this parameter qualifies the Path parameter. Enter a path element or pattern, such as "*.txt". Wildcard characters are permitted. The Include parameter is effective only when the command includes the contents of an item, such as C:\Windows\*, where the wildcard character specifies the contents of the C:\Windows directory.
Get-ChildItem won't produce any output unless you follow this pattern or (for some reason) include the -Name parameter. This might be why you started using the -Name parameter in the first place.
Final Answer
Get-ChildItem -Path "C:\PowerShell\BA\*" -Include "*.xlsx" |
ForEach-Object {
$xl = Open-ExcelPackage $_.FullName
$sheet1 = $xl.Workbook.Worksheets[1]
$sheet1.Name ="Sheet1"
Close-ExcelPackage $xl
}
If you have a large number of files and are able to install PowerShell 7, it might be possible to use the -Parallel parameter to speed up processing.
Get-ChildItem -Path "C:\PowerShell\BA\*" -Include "*.xlsx" |
ForEach-Object -Parallel {
$xl = Open-ExcelPackage $_.FullName
$sheet1 = $xl.Workbook.Worksheets[1]
$sheet1.Name ="Sheet1"
Close-ExcelPackage $xl
} -ThrottleLimit 5
If you're not a local admin, it is possible to get PowerShell 7 from the Store.
Get PowerShell - Microsoft Store
ForEach-Object (Microsoft.PowerShell.Core) - PowerShell | Microsoft Docs
What's New in PowerShell 7.0 - PowerShell | Microsoft Docs
Interpreting Error Messages
Either C:\WINDOWS\system32\ or $env:USERPROFILE is the default working directory when PowerShell is loaded.
Looking at our path:
C:\WINDOWS\system32\11.25.2020_JH_BDX.xlsx
We have one default directory and one custom file.
This is an indication that we're only providing the file name to our function. E.g. $_.Name
In our case, the root cause of this error is Get-ChildItem -Name which produces only file names as strings. It would be necessary to qualify those file names per Walter. Or to remove -Name and use the FullName property of FileInfo per Doug / Itchydon.
Open-ExcelPackage : Cannot bind argument to parameter 'Path' because it is null.
$xl= Open-ExcelPackage $i.fullname
Here there are two clues.
One, we just changed
$xl= Open-ExcelPackage $file
to
$xl= Open-ExcelPackage $i.fullname
So, $i.fullname must be the issue.
Two, we can trace the logic of the error message backward.
it is null -> parameter 'Path' -> Open-ExcelPackage Cannot bind -> Open-ExcelPackage $i.fullname
The two most likely possibilities are that $i or $i.FullName never existed in the first place.
If we check, we see that $i has no FullName property.
Debugging
Set a breakpoint for the $xl= Open-ExcelPackage $i.fullname line in your script file.
For example,
1 # C:\users\michael\desktop\vim\demo 1.ps1
2 $file = Get-ChildItem -Path "C:\users\michael\desktop\vim" -Name -Include *.out
3
4
5 foreach ($i in $file )
6 {
7 $xl= Open-ExcelPackage $i.fullname
8 $sheet1 = $xl.Workbook.Worksheets[1]
9 $sheet1.Name ="Sheet1"
10 Close-ExcelPackage $xl
11 }
> # from the shell
> Set-PSBreakpoint -Script '.\demo 1.ps1' -Line 7
ID Script Line Command Variable Action
-- ------ ---- ------- -------- ------
0 demo 1.ps1 7
> & '.\demo 1.ps1'
Entering debug mode. Use h or ? for help.
Hit Line breakpoint on 'C:\Users\Michael\desktop\vim\demo 1.ps1:7'
At C:\Users\Michael\desktop\vim\demo 1.ps1:7 char:1
+ $xl= Open-ExcelPackage $i.fullname
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> $i | gm
TypeName: System.String
Name MemberType Definition
---- ---------- ----------
Clone Method System.Object Clone(), System.Object ...
CompareTo Method int CompareTo(System.Object value), i...
Contains Method bool Contains(string value)
...
> # We have a string.
> $i | Get-Member | Where-Object { $_.name -match "^f" }
> # No match for properties starting with the letter 'f'.
> # So, strings don't have a 'FullName' property.
> q # to quit the debugger
Cannot bind argument to parameter 'Path' ...
Cannot index into a null array.
The property 'Name' cannot be found on this object...
Close-ExcelPackage : Cannot bind argument ...
Oftentimes resolving the first error will either fix the code or make the larger issue apparent.
For example, we know that $sheet1 = $xl.Workbook.Worksheets[1] (which depends on $xl being defined) won't work if $xl= Open-ExcelPackage $i.fullname didn't work.
There isn't much point in looking at the second, third, fourth, etc errors until the first one is resolved.
Pipelines
The two things to understand about pipelines are that:
Cmdlets are connected with pipes | (ASCII 124) and
An automatic variable is used to represent the current object being processed: $_
This gives us the basic tools to connect cmdlets cmdlet | cmdlet and to refer to the properties of the current object being processed ForEach-Object { $_.FullName }

Trying to use built-in PS1 methods Get-Date, Convert-Path, rename-item getting errors

I am new to PowerShell and trying to write my first script to copy files from one directory to another and then rename the files in the $DESTINATION directory once they are copied over there.
I am executing the script below from C:\Scripts directory on my machine.
The first foreach loop works great and my files land in the $DESTINATION.
But when I to through the 2nd loop I get the errors below
Any help/direction would be appreciated. Thanks.
Here is my code:
# Define variables.
$Source = "C:\ETLFILES\WinSCP\FilesETL\*.*"
$Destination = "C:\ETLFILES\WinSCP\SFE_Archive\"
$DestinationFiles = "C:\ETLFILES\WinSCP\SFE_Archive\*.*"
# Create the $sourceFileList variable to loop through
$sourceFileList = Get-ChildItem -path $Source
# Loop through the $soureFileList and copy the items to the $Destination.
foreach ($item in $sourceFileList) {
Copy-Item -Path $Source -Destination $Destination
}
# Create the $destinationFileList variable to loop through
$destinationFileList = Get-ChildItem -path $DestinationFiles
# Loop through the $destinationFileList and rename the files with appended DateTime stamp.
foreach ($itemDest in $destinationFileList) {
$Date = (Get-Date).ToString("yyyyMMdd_HHmmss")
$newFileName = $Date + "_" + $itemDest
Rename-Item $itemDest $newFileName
}
Here are the errors I'm getting and I think that I need to change the -path as it is looking to where the script is executing from and not looking at the $DestinationFiles directory like it looked to the $Source above:
PS C:\Scripts> C:\Scripts\ArchiveSFE_files.ps1
Rename-Item : Cannot rename the specified target, because it represents a path or device name.
At C:\Scripts\ArchiveSFE_files.ps1:21 char:5
+ Rename-Item $itemDest $newFileName
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Rename-Item], PSArgumentException
+ FullyQualifiedErrorId : Argument,Microsoft.PowerShell.Commands.RenameItemCommand
Rename-Item : Cannot rename the specified target, because it represents a path or device name.
At C:\Scripts\ArchiveSFE_files.ps1:21 char:5
+ Rename-Item $itemDest $newFileName
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Rename-Item], PSArgumentException
+ FullyQualifiedErrorId : Argument,Microsoft.PowerShell.Commands.RenameItemCommand
Rename-Item : Cannot rename the specified target, because it represents a path or device name.
At C:\Scripts\ArchiveSFE_files.ps1:21 char:5
+ Rename-Item $itemDest $newFileName
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Rename-Item], PSArgumentException
+ FullyQualifiedErrorId : Argument,Microsoft.PowerShell.Commands.RenameItemCommand
Rename-Item : Cannot rename the specified target, because it represents a path or device name.
At C:\Scripts\ArchiveSFE_files.ps1:21 char:5
+ Rename-Item $itemDest $newFileName
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Rename-Item], PSArgumentException
+ FullyQualifiedErrorId : Argument,Microsoft.PowerShell.Commands.RenameItemCommand
PS C:\Scripts>
PowerShell can be a bit tricky if you're used to text-based languages and tools, because everything in PowerShell is a .NET object.
This line:
$newFileName = Get-Date + "_" + $item # .ToString("yyyyMMdd-HHmmss") + "_" + $item
will most certainly result in an error. Get-Date returns a [datetime] object.
Although [datetime] does support the + operator, it expects an argument of the type [timespan] - and the string "_" can't be converted to a timespan.
What you want is a string representing the current date. Either use the ToString() statement that you've commented out, or use the -Format parameter to produce a formatted string instead of a [datetime] object:
$newFileName = (Get-Date).ToString("yyyyMMdd-HHmmss") + "_" + $item
or
$newFileName = (Get-Date -Format "yyyyMMdd-HHmmss") + "_" + $item
I'm not a betting man, but I'll bet the error you're having is this one.
Get-Date : Cannot bind parameter 'Date'. Cannot convert value "+" to type "System.DateTime".
Error: "String was not recognized as a valid DateTime."
This is because the code you've got here is trying to add a character to a PowerShell DateTime object, and that simply does not fly. You can pick a single property though and concatenate characters onto that, however.
But, I noticed that you commented out some lines in your code, and am guessing that you'd like to get the date in this format: "yyyyMMdd-HHmmss".
If that's so, this little snippet will do that for you:
# Define logic to rename and copy .
foreach ($item in $sourceFileList) {
$newFileName = $(Get-Date -UFormat %Y%m%d-%H%m%S) + "_" + $item
"the item $($item.BaseName) would become $newFileName"
}
This will output to the screen the new name for the file, like this:
the item q1 would become 20160921-110907_q1.png
the item q2 would become 20160921-110907_q2.png
the item q3 would become 20160921-110907_q3.png
You can comment out that line once you're happy with the new name. And the best part? Just drop this into your code inplace of your current $newFileName line and it will work with your previous code.
If I'm wrong, let me know the error you're getting and I'll help you get this sorted.
Looks like you are trying to write a file with : in the time part of the Get-Date when you try to rename to $newFileName. This will kick an error out.
$Date = Get-Date.ToString("yyyyMMdd_HHmmss")
$newFileName = $Date + "_" + $item
Include the above and that should prevent that problem.
thanks for everyone's help and input. I really appreciate it. I was finally able to resolve my problem whereby in the variable assigned in the foreach loop the FULL PATH was being defined that is what was generating the errors above. Once I used the .NAME property the issue was resolved. Thanks again to everyone.

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

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?

OutOfMemoryException when running my PowerShell script

Here's the PowerShell script I am using to add "segment99" to the beginning of all the text files (one by one) within a folder:
Set Environmental Variables:
$PathData = '<<ESB_Data_Share_HSH>>\RwdPnP'
Go to each text file in the specified folder and add header to the file:
Get-ChildItem $PathData -filter 'test_export.txt'|%{
$content = '"segment99" ' + [io.file]::ReadAllText($_.FullName)
[io.file]::WriteAllText(($_.FullName -replace '\.txt$','_99.txt'),$content)
}
This is giving me the following error:
Error: Exception calling "ReadAllText" with "1" argument(s): "Exception of type 'Syste
Error: m.OutOfMemoryException' was thrown."
Error: At D:\apps\MVPSI\JAMS\Agent\Temp\JAMSTemp13142.ps1:17 char:51
Error: + $content = '"segment99" ' + [io.file]::ReadAllText <<<< ($_.FullName)
Error: + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
Error: + FullyQualifiedErrorId : DotNetMethodException
Error:
I am running this code on a folder that has 20 files, each over 2 GB.
How can I fix this?
Copying a header file + a large file to a new file will be less prone to outofmemory exceptions (for files of that size):
$header = '"segment99"'
$header | out-file header.txt -encoding ASCII
$pathdata = "."
Get-ChildItem $PathData -filter 'test_export.txt' | %{
$newName = "{0}{1}{2}" -f $_.basename,"_99",$_.extension
$newPath = join-path (split-path $_.fullname) $newname
cmd /c copy /b "header.txt"+"$($_.fullname)" "$newpath"
}
This is not optimal code but it solves the task without reading all text to memory: it adds the header to the first line and then outputs other lines. Also, note that it does nothing if the input file is empty.
Get-ChildItem $PathData -Filter 'test_export.txt' | %{
$header = $true
Get-Content $_.FullName | .{process{
if ($header) {
'"segment99" ' + $_
$header = $false
}
else {
$_
}
}} | Set-Content ($_.FullName -replace '\.txt$', '_99.txt')
}