Powershell copy-item then test-path or copy-item with catch? - powershell

I have a script that moves files around in a production environment and currently performs a copy-item, then test-path, followed by remove-item if the test-path worked ok, similar to the below:
if ($copySuccess -eq $true) {
$files = Get-ChildItem $fileDir -Filter $filePrefix*.*
$files | ForEach-Object {
if ($copySuccess -eq $true) {
Copy-Item $fileDir\$_ -Destination $destDir
if (!(Test-Path $destDir\$_)) {
$copySuccess = $false
}
}
}
}
This method makes me feel comfortable as the test-path guarantees that the file is where it needs to be, before removing it.
I'm planning to rewrite parts of the script and I'm wondering if by using a copy-item with a catch on error, I can be sure that if no error is seen that the file has definitely been copied to the destination (without the need to use test-path as I assume this would make it quicker). As in below:
Get-ChildItem $fileDir -Filter $filePrefix*.* | ForEach {
if ($copySuccess -eq $true) {
try {
Copy-Item $fileDir\$_ -Destination $destDir -ErrorAction Stop
}
catch {
$copySuccess = $false
}
}
}
}
Of course, if there is a better way, please let me know (Powershell v5). The reason for this level of checking is that there are often network issues on the infrastructure, hence the test-path currently in use.

ErrorAction won't work in this case since:
The ErrorAction parameter has no effect on terminating errors (such as
missing data, parameters that are not valid, or insufficient
permissions) that prevent a command from completing successfully.
[Source]
If you want to check whether Copy-Item worked you can do this in a couple of ways to make sure.
The first one is to use $? variable:
Errors and Debugging: The success or failure status of the last
command can be determined by checking $?
Copy-Item $fileDir\$_ -Destination $destDir
if(-not $?) {
Write-Warning "Copy Failed"
}
Another method is by using the -Passthru parameter, we can capture the results to a variable. Note, this variable will only be populated if the operation was successful:
if(-not Copy-Item $fileDir\$_ -Destination $destDir -PassThru) {
Write-Warning "Copy Failed"
}

Related

Powershell script assistance. Adding exceptions.sites to the Java folder via GP login script

I was wondering if another set of eyes can spot where I'm going wrong.
I'm trying to add am exceptions.sites file to a users Java folder via GPO so as to allow for Edge Compatibility mode to work without user intervention. We have a lot of legacy applications and a lot of users so automating this would be great.
The script seems to run without error.
$Source = '\\thepath\Policies\{1FA921CE-231E-4982-8EBA-29E3AD4A44EF}\Machine\Scripts\Startup\exception.sites'
$users = (Get-ChildItem 'c:\Users\').name
$JavaFolder = "C:\Program Files\Java\"
$isJava = $false
$SEL = "Select string"
$DAFMComputer = "ControlPC"
#Do PC check to apply to only one PC
if ($DAFMComputer -like "ControlPC")
{
if (JavaPath -Path $JavaFolder) #if Java is installed
{
Foreach ($user in $users)
{
if (FileExists -Path "c:\Users\$user\AppData\LocalLow\Sun\Java\Deployment\security\exception.sites") #check for the file
{
$SEL = Select-String -Path "c:\Users\$user\AppData\LocalLow\Sun\Java\Deployment\security\exception.sites" -Pattern https://agapps.agriculture.gov.ie #Check for the string pattern
if ($SEL -ne $null)
{
#do nothing as the exception file exists with the URL we need do nothing
Copy-Item -Path \\thepath\Policies\{1FA921CE-231E-4982-8EBA-29E3AD4A44EF}\Machine\Scripts\Startup\exception.sites -Destination "c:\Temp\AlreadyThere\"
}
else
{
#add the following lines.
Add-Content "c:\Users\$user\AppData\LocalLow\Sun\Java\Deployment\security\exception.sites" "`nhttps://app.company.com/"
Add-Content "c:\Users\$user\AppData\LocalLow\Sun\Java\Deployment\security\exception.sites" "`nhttps://app.company.com/"
Add-Content "c:\Users\$user\AppData\LocalLow\Sun\Java\Deployment\security\exception.sites" "`We are adding lines"
Copy-Item -Path \\thepath\Policies\{1FA921CE-231E-4982-8EBA-29E3AD4A44EF}\Machine\Scripts\Startup\exception.sites -Destination "c:\Temp\FileExistsAddlines\"
}
}
else #file does not exist
{
Copy-Item -Path $Source -Destination "c:\Users\$user\AppData\LocalLow\Sun\Java\Deployment\security\exception.sites" -force
Copy-Item -Path \\thepath\Policies\{1FA921CE-231E-4982-8EBA-29E3AD4A44EF}\Machine\Scripts\Startup\exception.sites -Destination "c:\Temp\Filecopy\"
}
}
} # end file copy.
else
{
#No Java Do nothing
Copy-Item -Path \\thepath\Policies\{1FA921CE-231E-4982-8EBA-29E3AD4A44EF}\Machine\Scripts\Startup\exception.sites -Destination "c:\Temp\NoJavaDoNothing\"
}
else
{
Copy-Item -Path \\thepath\Policies\{1FA921CE-231E-4982-8EBA-29E3AD4A44EF}\Machine\Scripts\Startup\exception.sites -Destination "c:\Temp\NoComputerMatch\"
#do nothing as the PC name does not match
}
}
#Control line showing the script runs.
Copy-Item -Path \\thepath\Policies\{1FA921CE-231E-4982-8EBA-29E3AD4A44EF}\Machine\Scripts\Startup\exception.sites -Destination "c:\Temp\Running\"
The only line that reliably runs is the last control line on line 55 which copies a file to a folder. I've used similar copy file indicators in the if else loops to track and see where the loop is. But none of these are ever populated.
It's all the if elses are evaluating to false and no matter what i can't get it to enter the loop.
Any suggestions / pointers would be welcome
Thanks
John

Catch `Get-ChildItem` errors on a file-by-file basis

I'm using Get-ChildItem -Recurse to search a directory. I can't guarantee that everything Get-ChildItem will hit will be accessible. I want to log these failures, but not fail the entire Get-ChildItem -Recurse command. Right now I have
Get-ChildItem -Recurse $targetdir -ErrorAction Inquire `
| where { $_.Name -eq $name } `
| foreach {
echo-indented "Found $(hash $_) at $($_.FullName)"
$_
}
The code in question is the -ErrorAction Inquire. If I did -ErrorAction Stop, I would have to put a try-catch somewhere. It would have to be around the entire pipeline, right? In that case, childitems that would have been found after the inaccessible one will not be found and written out. So what else can I do?
For Get-ChildItem -Recurse, specifying the -ErrorAction won't really help you here. It will only cause access deny errors to either be:
Terminating (-ErrorAction Stop) where everything just stops. (not what you want)
Non-terminating (the default -ErrorAction Continue) which is what you want, as it will continue.
As for logs, with the default -ErrorAction Continue, all access denies are logged to the $Error variable. We can then parse through the exception records to get the information that we need:
#Start by clearing the error variable
$Error.Clear()
#Execute Get-ChildItem with -ErrorAction Continue
ls -Recurse $targetdir -ErrorAction Continue `
| where { $_.Name -EQ $name } `
| foreach {
echo-indented "Found $(hash $_) at $($_.FullName)"
$_
}
#Display objects we got Access Denies on:
$Error | ForEach-Object {
Write-Host $_.TargetObject
}
You can use the -ErrorVariable common parameter to save the errors to a variable.
Get-ChildItem -recurse foo -ErrorVariable err
$err

PowerShell mass copy to domain computers with multiple checks

I'm trying to copy a license file to all domain joined computers,
and i am only able to use PowerShell.
GPO or even (GPO) scheduled task is a no-go, because these don't seem to be possible yet within a Server 2003 environment.
The application this is regarding is already installed, and only the license file needs to be overwritten.
i am hoping to achieve:
- check if online, else skip
- check for 32b folder location, if exist, copy, else skip
- check for 64b folder location, if exist copy, else skip
- write every computers' result to a log file so i can prune successful
1st attempt;
Code i have currently:
$computers = gc "c:\temp\computers.txt"
$source = "c:\temp\almmodule.lic"
$dest32b = 'c$\program files (x86)\ALM - Automatic Login Module'
$dest64b = 'c$\program files\ALM - Automatic Login Module'
$TestPath32 = Test-Path -path "\\$computer\$dest32b\*"
$TestPath64 = Test-Path -path "\\$computer\$dest64b\*"
foreach ($computer in $computers) {
if (test-Connection -Cn $computer -quiet) {
IF (!$TestPath32) {
Copy-Item $source -Destination "\\$computer\$dest32b" -recurse -force -verbose}
ELSE {
"$computer' 32b folder not found. Skipping"}
IF (!$TestPath64) {
Copy-Item $source -Destination "\\$computer\$dest64b" -recurse -force -verbose}
ELSE
{"$computer 64b folder not found. Skipping"}
ELSE {
"$computer is not online"
}
}
}
I've tried some minor variations, but i can't seem to get anywhere.
Also the logging needs yet to be created.
Using myself as a test target, having run above variables, and using single commands;
$TestPath32 = Test-Path -path "\$computer\$dest32b*"
Returns: True
Copy-Item $source -Destination "\$computer\$dest32b" -recurse -force -verbose
is successful, and copies the file
Running the PowerShell at the moment complains about the last ELSE statement.
But most of the time it failed not recognizing i don't have a 64b folder, and it either gives an error, or places a file, with the directory as the filename.
I'm at a loss and have now tried so many things i'm afraid of braking the little that i've got.
2nd attempt;
I have edited the code and commented out some parts, just to get a working > model to progress from there.
foreach ($computer in $computers) {
$TestPath32 = Test-Path "\\$computer\$dest32b\*"
$TestPath64 = Test-Path "\\$computer\$dest64b\*"
# if (test-Connection -Cn $computer -quiet) {
IF (!$TestPath32) {
Copy-Item $source -Destination "\\$computer\$dest32b\" -recurse -force -verbose}
ELSE
{"$computer' 32b folder not found. Skipping"}
IF (!$TestPath64) {
Copy-Item $source -Destination "\\$computer\$dest64b\" -recurse -force -verbose}
ELSE
{"$computer 64b folder not found. Skipping"}
# ELSE {
# "$computer is not online"
# }
#}
}
Now returns :
PS C:\windows\system32> C:\Temp\ALM 2016 LIC copy.ps1
L2016010' 32b folder not found. Skipping
VERBOSE: Performing operation "Copy File" on Target "Item: C:\temp\almmodule.lic Destination: \\L2016010\c$\program files\ALM - Automatic Login Module\".
Copy-Item : De syntaxis van de bestandsnaam, mapnaam of volumenaam is onjuist.
At C:\Temp\ALM 2016 LIC copy.ps1:14 char:12
+ Copy-Item <<<< $source -Destination "\\$computer\$dest64b\" -force -verbose}
+ CategoryInfo : NotSpecified: (:) [Copy-Item], IOException
+ FullyQualifiedErrorId : System.IO.IOException,Microsoft.PowerShell.Commands.CopyItemCommand
i can assure you i DO have the 32b path. The copy-item doesn't place the file.
i DO NOT have the 64b path obviously, and i feel it breaks on this instead of just neatly returning $false like it should.
When i mess arround with he Path (because i thought that was the reason for the failure), it sometimes places a file in Program Files named "ALM - Automatic Login Module" which is the size of the license file.
Again, if i run the line Copy-Item $source -Destination "\\$computer\$dest32b\" as stand-alone, it DOES copy the file.
3rd attempt, now working;
$computers = gc "c:\temp\computers.txt"
$source = "c:\temp\almmodule.lic"
$dest32b = 'c$\program files (x86)\ALM - Automatic Login Module'
$dest64b = 'c$\program files\ALM - Automatic Login Module'
foreach ($computer in $computers) {
$TestUp = test-Connection -Cn $computer -quiet
$TestPath32 = Test-Path -pathType container "\\$computer\$dest32b"
$TestPath64 = Test-Path -pathType container "\\$computer\$dest64b"
IF (!$TestUp) {
write-host "$computer is not online"
} ELSE {
IF (!$TestPath32){
write-host "$computer' 32b folder not found. Skipping"
} ELSE {
Copy-Item $source -Destination "\\$computer\$dest32b\" -force -verbose
}
IF (!$TestPath64){
write-host "$computer 64b folder not found. Skipping"
} ELSE {
Copy-Item $source -Destination "\\$computer\$dest64b\" -force -verbose
}
}
}
The use of !$ totally went over my head, and i was supposed to work from a point of:
IF NOT, THEN, ELSE
Now the script skips folders not present, havn't been able to test a down computer yet, but i assume this now works also.
Now all i need to figure out, is how to log the output into 1 logfile, in a readable format
While using a GPO or a scheduled task would be better, or possibly deploying with SCCM if you've got it, there is an answer within your constraints. You've got the right basic idea, but move your assignments of $TestPath32 and $TestPath64 into the foreach loop:
...
foreach ($computer in $computers) {
if (test-Connection -Cn $computer -quiet) {
$TestPath32 = Test-Path -path "\\$computer\$dest32b\*"
$TestPath64 = Test-Path -path "\\$computer\$dest64b\*"
IF (!$TestPath32) {
...
I vote for GPO as well, as mentioned in the comments, you should definitely consider that.
However, if it doesn't help you,
Your $TestPath32 & $TestPath64 variables are evaluated before you assign your individual computer values. I would simply be null, and hence both will be $false. You can simply move them inside your for-loop,
foreach ($computer in $computers) {
if (test-Connection -Cn $computer -quiet) {
$TestPath32 = Test-Path -path "\\$computer\$dest32b\*"
$TestPath64 = Test-Path -path "\\$computer\$dest64b\*"
......
}
Also in the other hand I hope you have formatted the content of your file to return an array. If not you simply delimit it with "," and read
File Content: Computer1,Computer2,Computer3
and read the file as an array directly
$computers = Get-Content 'c:\temp\computers.txt' # It will read it as an array only. YOu do not need any additional formatting in this case.
Final version.
Couldn't get out-file to work like i wanted to.
Resorted to Start-Transcript.
Had to put Get-Content at the end to format the output to something that was readable.
#set-executionpolicy RemoteSigned
$computers = gc "c:\temp\computers.txt"
$source = "c:\temp\almmodule.lic"
$dest32b = 'c$\program files (x86)\ALM - Automatic Login Module'
$dest64b = 'c$\program files\ALM - Automatic Login Module'
Start-Transcript -path C:\temp\ALM-Log.txt -append
foreach ($computer in $computers) {
$TestUp = test-Connection -Cn $computer -quiet
$TestPath32 = Test-Path -pathType container "\\$computer\$dest32b"
$TestPath64 = Test-Path -pathType container "\\$computer\$dest64b"
IF (!$TestUp) {
write-host "$computer is not online`r"
} ELSE {
IF (!$TestPath32){
write-host "$computer' 32b folder not found. Skipping`r"
} ELSE {
Copy-Item $source -Destination "\\$computer\$dest32b\" -force -verbose
}
IF (!$TestPath64){
write-host "$computer 64b folder not found. Skipping`r"
} ELSE {
Copy-Item $source -Destination "\\$computer\$dest64b\" -force -verbose
}
}
}
Stop-Transcript
$log = Get-Content C:\temp\ALM-Log.txt
$log > c:\temp\ALM-Log.log
The actual solution as to why this works = Test-Path -pathtype container

Test-Path Move-Item Problems

I run this PowerShell script, and it works fine on PowerShell 4.0. But I now have PowerShell 5.0 and the script does work but it throws an error:
The Script:
$path = "X"
$destination = "Y"
while (Test-Path -Path $path) {
Move-Item -Path "$path\*zip" -Destination "$destination"
}
The error I get is:
Move-Item : The process cannot access the file because it is being
used by another process.
The title of the question: "Test-Path Move-Item Problems" implies that one cmdlet might be impacting the other. That doesn't make sense to me as Test-Path is checking the folder's existence and Move-Item is working on child items within that folder.
Personally I would not use a while loop for this use case as, once you have determined that the path exists you don't need to keep testing it:
if(Test-Path -Path $path){
Move-Item -Path $path\*zip -Destination $destination
}
just do it
Move-Item -Path "$path\*zip" -Destination "$destination" -ErrorAction Ignore

Powershell remove files

How to rewrite below code so it can be grouped into only one IF statements instead of multiple IF and only execute remove command if all files (outPath,outPath2, outPath3) are exists.
If ($outPath){
Remove-Item $outPath
}
If ($outPath2){
Remove-Item $outPath2
}
If ($outPath3){
Remove-Item $outPath3
}
If you want to be sure all the paths exist you can use Test-Path with -notcontains to get the results you want.
$paths = $outPath1, $outPath2, $outPath3
if ((test-path $paths) -notcontains $false){
Remove-Item -Path $paths
}
Test-Path works with arrays of paths. It will return an array booleans. If one of the paths didn't exist then a false would be returned. The if is conditional based on that logic.
Since you will only remove them if they exist you don't need to worry about their existence with the Remove-Item cmdlet.
this will remove the files if they exist and supress errors if the files are not found. The downside is that if there should be errors other than (path not found) then those would be supressed as well.
Remove-Item -Path $outPath,$outPath1,$outPath2 -ErrorAction SilentlyContinue
EDIT
if you only want to remove if all 3 files exist then:
if( (Test-Path $outPath) -and (Test-Path $outPath1) -and (Test-Path $outPath2) )
{
try
{
Remove-Item -Path $outPath,$outPath1,$outPath2 -ErrorAction Stop
}
Catch
{
throw $_
}
}