Copy script - right usage ?

Hi,

I’ve written a copy script which i am going to use in an SCCM 2012 R2 task sequence. Would like to know if i have used things in the correct way as part of my learning process…Thanks !

#Set Locations
#$StartLocation = “%windir%\temp”
#$Destination = “C:\ProgramData\Microsoft\User Account Pictures”

#Remove unrequired file(s)

[CmdletBinding(SupportsShouldProcess=$true)]

Param
(
  
)

Process
{

#Set Variables
$ErrorAction = ‘Stop’
$StartLocation = “%windir%\temp”
$Destination = “C:\ProgramData\Microsoft\User Account Pictures”
Try
{
$remove = get-childitem “$Destination” -Include *.dat -Recurse | Remove-item
}
Catch
{

    }
    
    If (!$remove)     
    {
        Write-Verbose -Message "An error occurred whilst attempting to remove $remove"
        Break
    }

#Copy Avatar files to location

Try
{
Get-ChildItem -path $StartLocation -Recurse -include “.bmp",".png” |
Foreach-Object { Copy-Item -path $_ -Destination $Destination }
}
Catch
{
Write-Verbose -Message “Failed to copy files to $Destination”
Break
}
}

Ah… well, did you have any more specific questions? I mean, if it works, that’s 90% of the battle!

Usually, you can pipe directly from Get-ChildItem to Copy-Item, which would probably run a tiny bit faster than using ForEach object.

I don’t think your Catch block will ever run, because Copy-Item normally doesn’t throw a terminating error. You’d need to add -ErrorAction Stop to have it generate a catchable, terminating exception. That’s true for your earlier Try/Catch, too - you might review “The Big Book of PowerShell Error Handling” (free ebook from our Resources menu) to learn more about that.

Oh, I see - you set $ErrorAction to ‘Stop.’ That won’t work. It’s $ErrorActionPreference, and setting it globally is considered a poor practice in most situations. Certainly where you’re not catching it globally.

Also, I’m not sure your intention, but as-is a single file copy failure will abort all remaining files, once your Try/Catch is working with -ErrorAction. If you were to get the files and then enumerate them, you could have it fail one file and continue trying others. Again, I’m not sure if that’s your intent or not, so this isn’t “wrong,” it’s just an observation.

You don’t technically need PROCESS{} because you’re not accepting pipeline input.

So, multiple observations:

Why are you trying to catch the errors? The reason you would stop doing a command is if your logic would be step X has to occur before you do step y. For instance, in the example below, if the removal fails, then the copy will not start because you are globally stopping the command if an error occurs. In your posted script you are just trying to catch errors just for logging?

I think a best practice is to use variables as much as possible. CommonApplicationData is a Special Folder that resolves to C:\ProgramData. So, in Windows 10, if you they move the directory to C:\ProgramMagicMSRules, your script would still work because you are using a provided variable that will resolve to that location. If you hardcode the path to C:\ProgramData, your script wouldn’t work. Make sense? Also, Powershell will not resolve %VAR%, you need to use $Env:VAR.

For Copy-Item, you don’t need to loop through each file. Run “Get-Help Copy-Item”. You will see that -Path accepts a String array (i.e. [string]). So, you should be able to run the command below and it will perform a Foreach for each column named “Path” in the return from Get-ChildItem

If you are trying to just capture errors, make them useful. If you were sitting at a computer and received an error, “An error occurred whilst attempting to remove…”, what would you do with that? You don’t have an actual error. Make sure if you do capture errors they are useful. See the examples that will show what the command is doing and the returned exception message that Catch captured

The below code was not tested, it’s just an example to illustrate some of the points above:

[CmdletBinding()]
$ErrorAction = 'Stop'
$Destination = "{0}\Microsoft\User Account Pictures" -f ([Environment]::GetFolderPath('CommonApplicationData'))
$StartLocation = "{0}\temp" -f $env:windir

try {
    Get-Childitem "$Destination" -Include *.dat -Recurse | Remove-item

    try {
        Copy-Item -Path (Get-ChildItem -Path $StartLocation -Recurse -include "*.bmp","*.png") -Destination $Destination
    }    
    catch {
        Write-Verbose -Message ("Error copying *.bmp and *.png to {0}. {1}" -f $StartLocation, $_.Exception.Message)
    }
}
Catch {
    Write-Verbose -Message ("Error occured removing *.dat from {0}. {1}" -f $Destination, $_.Exception.Message)
}

Thanks for your replies. I’m about 3 weeks into my learning powershell. I try and use it as much as I can. Must admit I’m getting confused when and where I should be using commands, like the try and catch.
Maybe I’m over complicating things. I want to be able to do best practice and people to look and say, good script !

Rob, your variables for destination and startlocation are great. But I’m not sure how it works together. I know what it is doing it’s the understanding. How and why did you come to that command? I’m impressed and would love to get to that level.

By asking the forum to check my work, I want to get into good habits from the start and not bad practice.

I’ve worked it out, by referring to Don’s great book, “Powershell in depth”.

Its quite clever ! In this example, “{0}\temp” -f $env:windir, the {0} is the first place hold. the -f means formatting, so the command after goes in the first {0} displaying C:\windows\temp !

Think thats about right ?

PowerShell is tricky, but awesome !!!

Sorry, can you help explain this line, Write-Verbose -Message (“Error occured removing *.dat from {0}. {1}” -f $Destination, $.Exception.Message)
How come the {0} has a . (dot) after it ? What does the $
.Exception.Message do and how is it called ?

{0} is a place holder for $Destination. So, basically the message would be: Error occured removing *.dat from C:\ProgramData\Microsoft\User Account Pictures. Exception Message

As for exceptions, when Powershell generates errors, there is a lot of information you can get which you can explore with Get-Member and other methods. Let’s look at what happens when I try to do something with C:\Windows\Temp when I’m logged in as a standard user. I get an Access Denied and other information like the type of exception, category, etc. when the error is shown. As I said earlier, we need to know what happened when the error occurred but there is also overkill. So, I just want the message “Access to the path ‘C:\Windows\Temp’ is denied.”, which would be $.Exception.Message. If you look at this, it’s 3 parts. The $ is a variable representing the current context of the script, which is what Catch is returning. $_ is an object which contains properties and methods. $.Exception is a property just like CategoryInfo, ErrorDetails, etc. (see below). We can use can use Get-Member to see what properties and methods are available with a returned object. Typically, the $.Exception.Message is descriptive enough to understand what happened with a command. If not, you have all of the information returned in Catch to give you as much information as you would need.

PS C:\Users\rsimmers> Get-ChildItem ("{0}\Temp" -f $env:windir)
Get-ChildItem : Access to the path 'C:\Windows\Temp' is denied.
At line:1 char:1
+ Get-ChildItem ("{0}\Temp" -f $env:windir)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : PermissionDenied: (C:\Windows\Temp:String) [Get-ChildItem], UnauthorizedAccessException
    + FullyQualifiedErrorId : DirUnauthorizedAccessError,Microsoft.PowerShell.Commands.GetChildItemCommand
 

PS C:\Users\rsimmers> try{Get-ChildItem ("{0}\Temp" -f $env:windir) -ErrorAction Stop}catch{"You got an error: {0}" -f $_.Exception.Message}
You got an error: Access to the path 'C:\Windows\Temp' is denied.


PS C:\Users\rsimmers> try{Get-ChildItem ("{0}\Temp" -f $env:windir) -ErrorAction Stop}catch{$_ | Get-Member}


   TypeName: System.Management.Automation.ErrorRecord

Name                  MemberType     Definition                                                                                                                                  
----                  ----------     ----------                                                                                                                                  
Equals                Method         bool Equals(System.Object obj)                                                                                                              
GetHashCode           Method         int GetHashCode()                                                                                                                           
GetObjectData         Method         void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context), void ISer...
GetType               Method         type GetType()                                                                                                                              
ToString              Method         string ToString()                                                                                                                           
CategoryInfo          Property       System.Management.Automation.ErrorCategoryInfo CategoryInfo {get;}                                                                          
ErrorDetails          Property       System.Management.Automation.ErrorDetails ErrorDetails {get;set;}                                                                           
Exception             Property       System.Exception Exception {get;}                                                                                                           
FullyQualifiedErrorId Property       string FullyQualifiedErrorId {get;}                                                                                                         
InvocationInfo        Property       System.Management.Automation.InvocationInfo InvocationInfo {get;}                                                                           
PipelineIterationInfo Property       System.Collections.ObjectModel.ReadOnlyCollection[int] PipelineIterationInfo {get;}                                                         
ScriptStackTrace      Property       string ScriptStackTrace {get;}                                                                                                              
TargetObject          Property       System.Object TargetObject {get;}                                                                                                           
PSMessageDetails      ScriptProperty System.Object PSMessageDetails {get=}                      



PS C:\Users\rsimmers> try{Get-ChildItem ("{0}\Temp" -f $env:windir) -ErrorAction Stop}catch{$_.Exception | Get-Member}


   TypeName: System.UnauthorizedAccessException

Name             MemberType Definition                                                                                                                                           
----             ---------- ----------                                                                                                                                           
Equals           Method     bool Equals(System.Object obj), bool _Exception.Equals(System.Object obj)                                                                            
GetBaseException Method     System.Exception GetBaseException(), System.Exception _Exception.GetBaseException()                                                                  
GetHashCode      Method     int GetHashCode(), int _Exception.GetHashCode()                                                                                                      
GetObjectData    Method     void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context), void ISerializable...
GetType          Method     type GetType(), type _Exception.GetType()                                                                                                            
ToString         Method     string ToString(), string _Exception.ToString()                                                                                                      
Data             Property   System.Collections.IDictionary Data {get;}                                                                                                           
HelpLink         Property   string HelpLink {get;set;}                                                                                                                           
HResult          Property   int HResult {get;}                                                                                                                                   
InnerException   Property   System.Exception InnerException {get;}                                                                                                               
Message          Property   string Message {get;}                                                                                                                                
Source           Property   string Source {get;set;}                                                                                                                             
StackTrace       Property   string StackTrace {get;}                                                                                                                             
TargetSite       Property   System.Reflection.MethodBase TargetSite {get;} 

Rob, thank you so much for taking the time to reply. So the lesson here is use get-member as much as possible and play with results of get-member, I guess.

Using things in the right way is important to me. I do get frustrated with my own knowledge, would love to get to your level of understanding. Just need to keep going with it.

Powershell is so cool !