Powershell: Copy-Item not working when in ForEach loop - powershell

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.

Related

Out-File when copying items and removing them

I got a script to take backup and to remove files in generations (group). I need to add some logging of which files it copies and also which ones it deletes. In all my previous scrips, I been using Out-File, but in this case for the copy I can't get it to work.
If I add it to the Copy-Item part it creates the file but it simply wont write any input. What I am missing?
#$a = Get-Date
#$a.ToUniversalTime()
foreach ($file in (Get-ChildItem -File $localpath -Recurse | Where {$_.LastWriteTime -gt (Get-Date).AddDays(-1)})) {
Copy-Item -Path $file.FullName -Destination "C:\qlikview Storage\privatedata\backup\$file.$(get-date -f yyyy-MM-dd)"
}
$Groups = Get-ChildItem -Path "C:\qlikview Storage\privatedata\backup" |
Group-Object -Property Basename |
Where-Object {$_.Count -gt 2}
foreach ($g in $Groups) {
$g.Group |
sort LastWriteTime -Descending |
select -Skip 2 |
foreach {del $_.FullName -Force}
}
The #a is for later to add timestamps the logging to see how long it takes.
Am I thinking wrong assuming Out-File is the way to go?
Add the -Verbose switch to your Copy-Item and Remove-Item commands. This will dump the copied/removed files to the verbose stream.
Afterwards you can redirect the verbose stream to the output stream (4>&1) and log it the a file.
Example :
Copy-Item... -Verbose 4>&1 | Out-file log.txt
Additional info can be found in about_Redirection.

Powershell Delete Files but keep last x version

I have a folder structure with, for example, 100 folders. Each folder has 200 files in it.
I would like to delete (via scheduled task) all files in each folder but keep the last 10 versions of it.
I am trying to upskill in Powershell so I am guessing that this should be pretty simple. I have created this script,
#Delete all files, keep last 10 versions#
$Directory = "D:\Octopus\Packages"
$Keep = "10"
Get-ChildItem $Directory| ?{ $_.PSIsContainer } | Select-Object FullName | Export-Csv $Directory\FolderList.csv
$FolderList = import-csv $Directory\FolderList.csv
ForEach ($row in $FolderList)
{
Get-ChildItem -Recurse | where{-not $_.PsIsContainer}| sort CreationTime -desc | select -Skip $Keep | Remove-Item -Force
}
It appears to be looping through each folder, but keeping the last 10 files for the entire folder structure, not per folder. So some folders have 0 files, some may have 2 files, some may have 8 files.
Any pointers would be appreciated
Thanks !
If you actually need to have that CSV then just modify Get-ChildItem -Recurse to Get-ChildItem $row -recurse. However, if you don't need to be creating the CSV, you can remove of that and just pipe the results of your first Get-ChildItem into the next action.
$Directory = "D:\Octopus\Packages"
$Keep = "10"
Get-ChildItem $Directory| ?{ $_.PSIsContainer } | Select-Object FullName |
ForEach-object {Get-ChildItem $_.fullname -Recurse |
where{-not $_.PsIsContainer}| sort CreationTime -desc |
select -Skip $Keep | Remove-Item -Force }

Loop through each Folder and delete some of them

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!

Powershell: Get-ChildItem - piping to Rename-Item and Copy-Item

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" } }

Compare-Object Delete File if file does not exist on source

I have this PowerShell code that compares 2 directories and removes files if the files no longer exist in the source directory.
For example say I have Folder 1 & Folder 2. I want to compare Folder 1 with Folder 2, If a file doesn't exist anymore in Folder 1 it will remove it from Folder 2.
this code works ok but I have a problem where it also picks up file differences on the date/time. I only want it to pick up a difference if the file doesn't exist anymore in Folder 1.
Compare-Object $source $destination -Property Name -PassThru | Where-Object {$_.SideIndicator -eq "=>"} | % {
if(-not $_.FullName.PSIsContainer) {
UPDATE-LOG "File: $($_.FullName) has been removed from source"
Remove-Item -Path $_.FullName -Force -ErrorAction SilentlyContinue
}
}
Is there an extra Where-Object {$file1 <> $file2} or something like that.?
I am not sure how you are getting the information for $source and $destination I am assuming you are using Get-ChildItem
What i would do to eliminate the issue with date/time would be to not capture it in these variables. For Example:
$source = Get-ChildItem C:\temp\Folder1 -Recurse | select -ExpandProperty FullName
$destination = Get-ChildItem C:\temp\Folder2 -Recurse | select -ExpandProperty FullName
By doing this you only get the FullName Property for each object that is a child item not the date/time.
You would need to change some of the script after doing this for it to still work.
If I am not getting it wrong, the issue is your code is deleting the file with different time-stamp as compared to source:
Did you try -ExcludeProperty?
$source = Get-ChildItem "E:\New folder" -Recurse | select -ExcludeProperty Date
The following script can serve your purpose
$Item1=Get-ChildItem 'SourcePath'
$Item2=Get-ChildItem 'DestinationPath'
$DifferenceItem=Compare-Object $Item1 $Item2
$ItemToBeDeleted=$DifferenceItem | where {$_.SideIndicator -eq "=>" }
foreach ($item in $ItemToBeDeleted)
{
$FullPath=$item.InputObject.FullName
Remove-Item $FullPath -Force
}
Try something like this
In PowerShell V5:
$yourdir1="c:\temp"
$yourdir2="c:\temp2"
$filesnamedir1=(gci $yourdir1 -file).Name
gci $yourdir2 -file | where Name -notin $filesnamedir1| remove-item
In old PowerShell:
$yourdir1="c:\temp"
$yourdir2="c:\temp2"
$filesnamedir1=(gci $yourdir1 | where {$_.psiscontainer -eq $false}).Name
gci $yourdir2 | where {$_.psiscontainer -eq $false -and $_.Name -notin $filesnamedir1} | remove-item
If you want to compare files in multiple dir, use the -recurse option for every gci command.