I am trying to write a script that gathers a list of server names, then iterates through each server name and grabs the second to the newest folder in a directory, renames it, and copies it to another server. I am running into trouble with getting this to work all in one command, and I am not sure what is the best way to split it up and have it work properly. I get errors when trying different things, the main one is "rename-item: cannot evaluate parameter "NewName" because its argument is specified as a script block and there is no input". Here is the code snippet that causes this error:
$serverNames = Get-Content -Path "C:\servers.txt"
foreach ($serverName in $ServerNames) {
$reportServer = "a56741035"
Get-ChildItem "\\$($serverName)\d$\mposlogs\device" | where { $_.PSIsContainer } | Sort CreationTime -Descending | Select -Skip 1 | Select -First 1 | Rename-Item -NewName { "$serverName" + "_" + $_.Name } | Copy-Item $_.FullName -Destination "\\$($reportServer)\c$\temp\mpos\logs" }
Another is "cannot bind argument to parameter 'Path' because it is null". Here is the code that causes this error:
$serverNames = Get-Content -Path "C:\servers.txt"
foreach ($serverName in $ServerNames) {
$reportServer = "a56741035"
Get-ChildItem "\\$($serverName)\d$\mposlogs\device" | where { $_.PSIsContainer } | Sort CreationTime -Descending | Select -Skip 1 | Select -First 1 | ForEach-Object { Rename-Item -NewName { "$serverName" + "_" + $_.Name } | Copy-Item $_.FullName -Destination "\\$($reportServer)\c$\temp\mpos\logs" } }
I feel like I am very close, and just don't see something. Any assistance is greatly appreciated, thank you so much. Have a great day.
EDITED TO INCLUDE NEWEST CODE:
Get-ChildItem "\\$($serverName)\d$\mposlogs\device" | where { $_.PSIsContainer } | Sort CreationTime -Descending | Select -Skip 1 | Select -First 1 | ForEach-Object { Rename-Item -Path $_.FullName -NewName ( "$serverName" + "_" + $_.Name ) ; Copy-Item $_.FullName -Destination "\\$($reportServer)\c$\temp\mpos\logs" }
CODE FOR NEWEST COMMENTS:
Get-Child-Item "\\$serverName\d$\mposlogs\device" | where {$_.PSIsContainer} | Sort CreationTime -Descending | Select -Skip 1 | Select -First 1 | ForEach-Object { Rename-Item -Path $_.FullName -NewName ( "$serverName" + "" + $.Name ) ; Copy-Item ( "$serverName" + "" + $.Name ) -Destination "\\$reportServer\c$\temp\mpos\logs" }
So this turned out to be more difficult than I thought it would be, the trick being that Rename-Item seems to basically consume the pipelined object without giving you a chance to use that object in the subsequent name(using a scriptblock is also not really ideal, much easier to use a simple concatted string) so you need to change the Rename-Item portion of your loop slightly to
Rename-Item -Path $_.FullName -NewName ("$serverName" + "_" + $_.Name)
As far as the syntax changes prageeth suggests you can make them but they are unneeded and imo increase ambiguity(Get-Childitem does not require an escape character to access an admin share and there is nothing wrong with using $($) syntax even when not accessing a property).
EDIT
Ok after working in the comments for a bit here is an updated version of the full GCI pipeline. I tested it successfully in my environment so fingers crossed it works for you. Please let me know if any part of it doesn't make sense.
Get-ChildItem "\\$serverName\d$\mposlogs\device" | where {$_.PSIsContainer} | Sort CreationTime -Descending | Select -Skip 1 | Select -First 1 | ForEach-Object { Rename-Item -Path $_.FullName -NewName ( "$serverName" + "_" + $_.Name ) -PassThru | Copy-Item -Destination "\\$reportServer\c$\temp\mpos\logs" }
In situation like this is always best to analyse the command output first...
# Rename doesn't output anything by default
Rename-Item ToBeRenamed.txt Renamed.txt
# For most commands with that default behaviour, you can use -PassThru to force output
Rename-Item ToBeRenamed.txt Renamed.txt -PassThru
# Once we have something in the pipeline, we can pipe result to Copy-Item
Rename-Item ToBeRenamed.txt Renamed.txt -PassThru | Copy-Item -Destination C:\temp
ls C:\temp\Renamed.txt
I would recommend avoiding ForEach-Object here - it's not really needed. You just need to control output from commands, that's it.
Edit:
As user mike mentioned, it appears that admin shares doesn't need $ to be escaped with. The real problem is rename-Item cmdlet missing the -Path parameter. Thanks for bringing that up Mike!
However I still recommend removing the extra $ over $($serverName) to reduce ambiguity.
Try this!
Get-ChildItem "\\$serverName\d$\mposlogs\device" | where { $_.PSIsContainer } | Sort CreationTime -Descending | Select -Skip 1 | Select -First 1 | ForEach-Object { Rename-Item -Path $_.FullName -NewName { "$serverName" + "_" + $_.Name } | Copy-Item $_.FullName -Destination "\\$reportServer\c$\temp\mpos\logs" } }
Related
I've tried to rename a csv with powershell and then move it automatically to another folder, when there's no file.
Originally, the csv-name looks like this: import_9999_2020-08-13_132238.csv but the part with 9999 can also include just 2 or 3 digits.
My actual Code looks like:
#Import of path and target-path
$path = "\\network-path\subfolder\subfolder\subfolder\subfolder\subfolder1\"
$target_path = "\\network-path\subfolder\subfolder\subfolder\subfolder\subfolder2\"
#endless loop
$a=$true
while($a -eq $true){
$Files = gci $path
$TargetFiles = gci $target_path
#wait 5 minutes if path is empty
if(($Files).Count -eq 0){
sleep -Seconds 300
}
#if path is filled with one or more files
else {
#if file in target-path is processed (from another program)
if(($TargetFiles).count -eq 0){
#rename and move the latest file
get-childitem -path $path -Filter "import_*.csv"|
where-object { -not $_.PSIsContainer } |
sort-object -Property $_.CreationTime |
select-object -last 1 |
Rename-Item -NewName {($_.Name.Substring(0,($_.Name.Length)-22))+".csv"} |
Move-Item -Destination $target_path +"$($_.Name).csv"
}
sleep -Seconds 20
}
}
It works partly and renames the csv, but it doesn't move it to the target-path. The path is correct, i've copied it from the Windows-Explorer.
Any Ideas, why the program doesn't work completely? Thanks
For better readability, I would split the code where you find the original file, rename it and move it to the target path into several lines.
Also, Move-Item can rename the file aswell, so no need to do a Rename-Item first:
$file = Get-ChildItem -Path $path -Filter "import_*.csv" -File |
Sort-Object -Property $_.LastWriteTime |
Select-Object -Last 1
# create the new name for the file.
# or use regex: $newName = '{0}{1}' -f ($file.BaseName -replace '(_\d{4}-\d{2}-\d{2}_\d+)$'), $file.Extension
$newName = '{0}{1}' -f (($file.BaseName -split '_')[0..1] -join '_'), $file.Extension
# now move the file and rename at the same time
$file | Move-Item -Destination (Join-Path -path $target_path -ChildPath $newName)
I changed CreationTime to LastWriteTime to really get the latest file.
I also added the -File switch to the Get-ChildItem cmdlet, because only for old PowerShell versions you need to use where-object { -not $_.PSIsContainer }
If you want to pass it down the pipeline add -Passthru to the Rename-Item cmdlet
Somefile.txt | Rename-Item -NewName {$_.basename + "abc" + $_.ext} | # nothing in the pipeline
Somefile.txt | Rename-Item -NewName {$_.basename + "abc" + $_.ext} -Passthru | # now the new named fileinfo object is in the pipeline, contained in automatic variable $_
I'm trying to run the script below in multiple paths using an array. My goal is to delete folders keeping the last 7 versions, but it is not working as expected. The action is only taking into account the first path D:\Test1.
I believe that I should add something like ($folders in $folders) after ForEach-Object but I don know how.
Any idea what I missing here?
$path = #("D:\Test1","D:\Test2","D:\Test3")
$folders = Get-ChildItem -Path $path -Recurse |
Where-Object { $_.PSIsContainer } |
Group-Object { $_.Name.Split('_')[0] } |
ForEach-Object $Folders {
$_.Group |
sort CreationTime -Descending |
Select -Skip 7 |
foreach { Remove-Item $_.FullName -Force -WhatIf }
}
This should do your job.
$path= #("D:\Test1","D:\Test2","D:\Test3")
$folders= Get-ChildItem -path $path -Recurse | Where-Object {$_.PsIsContainer} |Group-Object {$_.FullName.Split('_')[0] }
ForEach($folder in $folders)
{
$folder.Group | sort CreationTime -Descending | Select -Skip 7|% { Remove-Item $_.fullname -Force -whatIf}
}
I tested in my local and it is working fine. Although I didn't get any error in your code except few formatting issue which I have taken into variable and sorted it out cause I got tangled in too many pipeline objects.
If you are using foreach after a pipeline , that means it will take the pipeline objects one by one only. But if you are separately using it , then you have to assign each iteration into a variable.
Hope it helps you.
I got the answer from #Robert Israelsson !
" If you change your group-object to not group by name but instead fullname you will get the desired result."
From:
$folders= Get-ChildItem -path $path -Recurse | Where-Object {$_.PsIsContainer} |Group-Object {$_.Name.Split('_')[0] }
To:
$folders= Get-ChildItem -path $path -Recurse | Where-Object {$_.PsIsContainer} |Group-Object {$_.FullName.Split('_')[0] }
And this works perfectly!
I'm having ongoing trouble with a script I've written that is (supposed) to do the following.
I have one folder with a number of csv files, and I want to copy the latest file with the company name into another folder, and rename it.
It is in the current format:
21Feb17070051_CompanyName_Sent21022017
I want it in the following format:
CompanyName21022017
So I have the following powershell script to do this:
## Declare variables ##
$DateStamp = get-date -uformat "%Y%m%d"
$csv_dest = "C:\Dest"
$csv_path = "C:\Location"
## Copy latest Company CSV file ##
get-childitem -path $csv_path -Filter "*Company*.csv" |
where-object { -not $_.PSIsContainer } |
sort-object -Property $_.CreationTime |
select-object -last 1 |
copy-item -Destination $csv_dest
## Rename the file that has been moved ##
get-childitem -path $csv_dest -Filter "*Company*.csv" |
where-object { -not $_.PSIsContainer } |
sort-object -Property $_.CreationTime |
select-object -last 1 | rename-item $file -NewName {"Company" + $DateStamp + ".csv"}
The file seems to copy ok, but the rename fails -
Rename-Item : Cannot bind argument to parameter 'Path' because it is null.
At C:\Powershell Scripts\MoveCompanyFiles.ps1:20 char:41
+ select-object -last 1 | rename-item $file -NewName {"CompanyName" + $DateSt ...
I think it is something to do with the order in which powershell works, or the fact it can't see the .csv in the $file variable. There are other files (text files, batch files) in the destination, in case that affects things.
Any help in where I'm going wrong would be appreciated.
As wOxxOm answered, you need to remove $file from Rename-Item as it is not defined and the cmdlet already receives the inputobject through the pipeline.
I would also suggest that you combine the two operations by passing through the fileinfo-object for the copied file to Rename-Item. Ex:
## Declare variables ##
$DateStamp = get-date -uformat "%Y%m%d"
$csv_dest = "C:\Dest"
$csv_path = "C:\Location"
## Copy and rename latest Company CSV file ##
Get-ChildItem -Path $csv_path -Filter "*Company*.csv" |
Where-Object { -not $_.PSIsContainer } |
Sort-Object -Property CreationTime |
Select-Object -Last 1 |
Copy-Item -Destination $csv_dest -PassThru |
Rename-Item -NewName {"Company" + $DateStamp + ".csv"}
You can rename and copy in a single command. Just use Copy-Item Command and give new path and name as -Destination parameter value. It will copy and rename the file. You can find an example below.
$source_path = "c:\devops\test"
$destination_path = "c:\devops\test\"
$file_name_pattern = "*.nupkg"
get-childitem -path $source_path -Filter $file_name_pattern |
Copy-Item -Destination { $destination_path + $_.Name.Split("-")[0] + ".nupkg"}
The script I am including below needs to accomplish the following tasks. It needs to get a list of servers from AD, then iterate through each of those server names and grab the second to the newest folder in a directory, rename it, and copy it to another server.
The Copy-Item command is not working when I have it in the foreach loop, as written below:
#gathering server names
$serverList = (Get-ADComputer -Filter "Name -like 'Q0*00*'" -SearchBase "OU=MPOS,OU=Prod,OU=POS,DC=N,DC=NET").name | Sort-Object | Out-File C:\Temp\MPOS\MPOSServers.txt
$serverListPath = "C:\Temp\MPOS\MPOSServers.txt"
#Retrieve a list of MPOS Print servers from text file and set to $serverNames
$serverNames = Get-Content -Path $serverListPath
#Iterate through each of the server names
foreach ($serverName in $serverNames) {
$reportServer = "a03"
Get-ChildItem "\\$($serverName)\d$\MPosLogs\Device" |
Where { $_.PSIsContainer } |
Sort CreationTime -Descending |
Select -Skip 1 |
Select -First 1 |
ForEach-Object {
Rename-Item -Path $_.FullName -NewName ("$serverName" + "_" + $_.Name) -PassThru |
Copy-Item -Destination "\\$($serverName)\c$\temp\MPOS\Logs"
}
}
However, it works fine if I am testing it outside of the ForEach loop, as written below:
Get-ChildItem "\\$($serverName)\d$\MPosLogs\Device" |
Where { $_.PSIsContainer } |
Sort CreationTime -Descending |
Select -Skip 1 |
Select -First 1 |
ForEach-Object {
Rename-Item -Path $_.FullName -NewName ("$serverName" + "_" + $_.Name) -PassThru |
Copy-Item -Destination "\\$($serverName)\c$\temp\MPOS\Logs"
}
Any ideas as to why it is not working in the full script? I am not changing anything when I test it, I am just running the above commands without being in the ForEach loop. It is completing the rest of the tasks, except for the folder copies. The folder copy only works if I am testing it outside of the ForEach loop on a single server.
When I say "it doesn't work", there are no errors or anything like that. It simply is not copying the folders.
Thank you! :)
LG
#MikeGaruccio well that is extremely embarrassing. I think I have just been staring at this script for too long, and did not realize that I was not actually copying the folders to $reportServer - it's a good thing you asked!!! It definitely matters. All is well now, after changing the final $serverName to actually read $reportServer. Thank you, and sorry for wasting your time...I appreciate your help a lot.
Calling this powershell command and getting an error. Driving me nuts.
Prompt> get-childitem -recurse ./ *NYCSCA* | where-object { $_.Name -like
"*NYCSCA*" } | rename-item $_ -newname $_.Name.Replace(" ","_") -whatif
Here is the response:
You cannot call a method on a null-valued expression.
At line:1 char:140
+ get-childitem -recurse ./ *NYCSCA* | where-object { $_.Name -like "*NYCSCA*" } | select FullName | rename-item $_ -n
ewname $_.Name.Replace <<<< (" ","_") -whatif
+ CategoryInfo : InvalidOperation: (Replace:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
If I remove the last part, I get a list of files. Any clues? I have not grocked powershell yet, obviously.
Note: I tried to post this to superuser, but the site is consistently failing now - won't let me add this exact question.
Here it is greatly simplified. I cannot even get this classic example to work.
gci *NYCSCA* | ren $_ ($_.Name).Replace("foo","bar")
Thank you #JNK, the % did it. The solution I needed is this, in case you're interested:
gci -recurse | where-object{ $_.Name -like "*NYCSCA*"} | %{rename-item $_.FullName $_.FullName.Replace("NYCSCA","SDUSD") }
I think you need foreach-object:
get-childitem -recurse ./ *NYCSCA* | where-object { $_.Name -like
"*NYCSCA*" } | % {rename-item $_ -newname $_.Name.Replace(" ","_") -whatif}
The piped array can't be renamed as a set.
Here's a simplified version to rename files only
Get-ChildItem -Filter *NYCSCA* -Recurse |
Where-Object {!$_.PSIsContainer} |
Rename-Item -NewName { $_.Name.Replace(' ','_') } -WhatIf
(Edit: line breaks added for clarity)