Last Update: Sep 04, 2024 | Published: Oct 15, 2014
Not too long ago, I chimed in on a PowerShell problem about copying files on some social media platform. An IT pro was facing a challenge using PowerShell to get his job done and wasn’t sure what to do. The task at hand was to copy files to a network location using alternate credentials. Naturally, he wanted this accomplished using PowerShell. At first glance, you might think this should be pretty easy. If you look at help for Copy-Item, you will see a credential parameter. However, if you look at help for this parameter, then you will come across a rather stunning pronouncement.
Yes. This parameter is not used out-of-the-box. This is why it is important that you be in the habit of reading full help in PowerShell. More than likely this parameter exists for future providers or future versions of existing providers that will support it. But for right now in PowerShell 4.0, this is not an option. So if we can’t use an alternate credential, how can we copy files?
My solution is to leverage the New-PSDrive cmdlet. This cmdlet has a credential parameter that will work. I should be able to create a new drive to a network share using an alternate credential.
new-psdrive -Name IT -PSProvider FileSystem -Root chi-fp02IT -Credential globomanticsadministrator
Now I can copy files to this destination.
dir c:work*.xml | copy -Destination IT: -PassThru
When I’m finished copying I can remove the PSDrive.
Remove-PSDrive IT
But it would sure be nice to make this process more transparent. So I created a proxy function for Copy-Item. A proxy function is wrapper for a PowerShell command that you can customize. Often proxy commands add or remove parameters to an underlying cmdlet. I used a function that I published on my blog to create a proxy version of Copy-Item. Here is a version that I call Copy-Item2 because I didn’t want to overwrite the original Copy-Item2, although you certainly could.
#requires -version 2.0 #copy item with credential Function Copy-Item2 { <# .Synopsis A Copy-Item replacement that supports alternate credentials .Description This is a proxy version of Copy-Item that has been modified to support using alternate credentials when copying files to a UNC. .Parameter Credential Enter a saved PSCredential object or an account name in the format domainusername or computernameusername. You will be prompted for the password. This parameter only works if the destination is a UNC. .Notes Last Updated: 9/25/2014 .Example PS C:> dir c:work*.xml | Copy-Item2 -destination chi-fp02IT -credential globomanticsadministrator -passthru .Link Copy-Item #> [CmdletBinding(DefaultParameterSetName='Path', SupportsShouldProcess=$true, ConfirmImpact='Medium', SupportsTransactions=$true, HelpUri='http://go.microsoft.com/fwlink/?LinkID=113292')] param( [Parameter(ParameterSetName='Path', Mandatory=$true, Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [string[]]$Path, [Parameter(ParameterSetName='LiteralPath', Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [Alias('PSPath')] [string[]]$LiteralPath, [Parameter(Position=1, ValueFromPipelineByPropertyName=$true)] [string]$Destination, [switch]$Container, [switch]$Force, [string]$Filter, [string[]]$Include, [string[]]$Exclude, [switch]$Recurse, [switch]$PassThru, [Parameter(ValueFromPipelineByPropertyName=$true)] [ValidateScript({ if ($Destination -match "^\S+S+") { $True } else { Throw "You can only use the credential parameter with a UNC destination." } })] [System.Management.Automation.CredentialAttribute()]$Credential ) begin { Write-Verbose -Message "Starting $($MyInvocation.Mycommand)" try { $outBuffer = $null if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) { $PSBoundParameters['OutBuffer'] = 1 } if ($Credential) { #create a temporary PSDrive with the credential Write-Verbose -Message "Creating temporary PSDrive to $Destination" Try { #NOTE: this code does not always work #generate a random name without an extension $tmpName = [System.IO.Path]::GetFileNameWithoutExtension([System.IO.path]::GetRandomFileName()) Write-Verbose "Creating new psdrive $tmpName" Write-verbose "Using credential for $($credential.username)" $tmpDrive = New-PSDrive -Name $tmpName -PSProvider FileSystem -Root $Destination -Credential $Credential -ErrorAction Stop if (-not (Test-Path "$($tmpName):")) { Throw "New-PSDrive command failed mapping $Destination to $tmpName" } } Catch { #error creating PSDrive Throw } #Change PSBoundParameter Write-Verbose "Setting destination parameter to $($tmpName):" $PSBoundParameters.Destination = "$($tmpName):" #remove the credential from PSBound parameters Write-Verbose "Removing original credential from PSBoundParameters" $PSBoundParameters.Remove("Credential") | Out-Null }#if Credential $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Copy-Item', [System.Management.Automation.CommandTypes]::Cmdlet) $scriptCmd = {& $wrappedCmd @PSBoundParameters } $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin) $steppablePipeline.Begin($PSCmdlet) } catch { throw } } process { try { $steppablePipeline.Process($_) } catch { throw } } end { try { $steppablePipeline.End() } catch { throw } if (Test-Path "$($tmpName):") { Write-Verbose "Removing temporary PSDrive" Remove-PSDrive -Name $tmpName #might need to clean up a NET USE Drive mapping #need to escape any in the path $wmiPath = $Destination.replace("","") $net = Get-WMIObject -Class Win32_networkConnection -filter "RemoteName='$wmiPath'" if ($net) { net use $Destination /delete | out-Null } } Write-Verbose -Message "Ending $($MyInvocation.Mycommand)" } } #end function Copy-Item2
I modified the credential parameter to include a validation test, verifying that the destination is a UNC. If it isn’t, then PowerShell will throw an exception when you run the command.
[Parameter(ValueFromPipelineByPropertyName=$true)] [ValidateScript({ if ($Destination -match "^\S+S+") { $True } else { Throw "You can only use the credential parameter with a UNC destination." } })] [System.Management.Automation.CredentialAttribute()]$Credential )
In the script, if –Credential is specified, I want to create a temporary PSDrive. Because I don’t want to have to guess what might be an available drive letter or name, I’ll have the .NET Framework give me random file name which will be something like ‘3bani1qf.gie’. Then I get the filename only, i.e. ‘3bani1qf’.
All of this done in a one-line command:
$tmpName = [System.IO.Path]::GetFileNameWithoutExtension([System.IO.path]::GetRandomFileName())
Armed with a name, I can create the PSDrive.
$tmpDrive = New-PSDrive -Name $tmpName -PSProvider FileSystem -Root $Destination -Credential $Credential -ErrorAction Stop
Now, because Copy-Item is a wrapped command, it doesn’t really use Copy-Item, I don’t want it to see the credential parameter, so I’ll drop it from the bound parameters.
$PSBoundParameters.Remove("Credential") | Out-Null
I will also redefine the destination parameter to use the newly created PSDrive.
$PSBoundParameters.Destination = "$($tmpName):"
The proxy function will invoke the underlying cmdlet with the defined parameters.
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Copy-Item', [System.Management.Automation.CommandTypes]::Cmdlet) $scriptCmd = {& $wrappedCmd @PSBoundParameters }
With this function loaded in my PowerShell session I can use Copy-Item, or at least my version, as you would expect:
dir c:work*.xml | Copy-Item2 -destination chi-fp02IT -credential globomanticsadministrator -passthru
Hopefully if you use this command it will always work. But in my testing it would never work 100% of the time. You might see an error about multiple connections to a server not being allowed. I’ll have another alternative next week.