PowerShell Problem Solver: Copy Files with Credentials Revisited

In the last PowerShell Problem Solver, I shared a proxy function to copy files to a network share using alternate credentials. You might want to take a moment to look at that article before continuing. As I stated at the end of the article, most of the time using the temporary PSDrive works just fine.

Troubleshooting the use of PSDrive to copy files

In my testing, I eventually ran into an error “Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again.” What makes this difficult to troubleshoot is that it isn’t always apparent where this connection is defined. I used WMI to query for network connections.

get-wmiobject win32_networkconnection

I even checked using the legacy NET USE command to see if there were any lingering connections. Sometimes you can be left with a mapped connection to the IPC$ share. If that happens, then you can easily delete it.

net use \chi-fp02ipc$ /delete

Rebooting didn’t always resolve this issue. So I decided to take a different approach in my Copy-Item proxy function. Sometimes the old school ways are the best ways. I still wanted a mapped drive using alternate credentials so why not use NET USE? The syntax for mapping a drive with credentials is:

NET USE <driveletter> <share UNC> /user:<username> <password>

To use this in PowerShell requires a little work. First, the NET USE command can only use a single letter for the drive mapping. Since I have no way of knowing what drive letters are in use, I’ll use PowerShell to programmatically figure it out.

$drv = dir function:[e-z]: -Name | Where { -Not (test-path $_ )} | select -first 1

When you start PowerShell, it automatically creates a function for every drive letter. That’s why when you type D: at a PowerShell prompt, you change location to the D: drive. In the one-liner above, I’m getting a directory listing of functions E: through Z:. The [e-z] is a regular expression pattern. The –Name parameter means the first part of the expression will only write something like D: to the pipeline, in other words a filesystem location. These locations are piped to Where-Object which tests if the drive exists or not. If it doesn’t exist, PowerShell writes the object to Select-Object where I am selecting only the first drive letter not in use.

Credentials, NET USE, and PSCredential

The credential part is a little trickier because the NET USE command has no concept of a PSCredential and I don’t want to have to include a Password parameter. So here’s how you can break apart a PSCredential object. The name part is stored in the Username parameter. However, the password is stored as a secure string, meaning it is encrypted in memory on your computer. The NET USE command is expecting plain text. The trick is to invoke the Get-NetworkCredential() method on the PSCredential object. This will break the PSCredential down, including a Password property with a clear next value! This is why it is important that you lock your desktop when stepping away if using PSCredentials in your PowerShell session. While the credential goes away when PowerShell ends, anyone with access to your computer and some PowerShell knowledge could quickly find and decipher credential password. The end result is that I can use the NET USE command to map a temporary drive using credentials, delivered from PowerShell.

net use $drv $destination /user:$($credential.username) $Credential.GetNetworkCredential().Password

As with the previous version of my script, I can change the destination parameter.

$PSBoundParameters.Destination = "$($drv)"

The wrapped Copy-Item cmdlet will use this destination. At the end, I’ll remove the NET USE mapping.

if (Test-Path -path $drv) {
  Write-Verbose "Removing temporary drive mapping"
  net use $drv /d | Out-Null
}


Here is another version of my Copy-Item2 function that uses the NET USE command.

#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)]
 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, Mandatory=$True,HelpMessage="Enter the destination path",
     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 "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 {
                #get a free drive letter
                $drv = dir function:[e-z]: -Name | Where { -Not (test-path $_ )} | select -first 1
                Write-verbose "Using credential for $($credential.username)"
                net use $drv $destination /user:$($credential.username) $Credential.GetNetworkCredential().Password | out-null
                #verify command completed
                if (-not (Test-Path "$($drv)")) {
                    Throw "Net Use command failed: net use $drv $destination /user:$($credential.username) $($Credential.GetNetworkCredential().Password)"
                }
             }
             Catch {
                Throw
             }
             #Change PSBoundParameter to reflect new destination
             Write-Verbose "Setting destination parameter to $($drv):"
             $PSBoundParameters.Destination = "$($drv)"
             #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
     }
 } #begin
 process {
     try {
         $steppablePipeline.Process($_)
     } catch {
         throw
     }
 } #process
 end {
     try {
         $steppablePipeline.End()
     } catch {
         throw
     }
     #remove temporary drive mapping
     if (Test-Path  -path $drv) {
       Write-Verbose "Removing temporary drive mapping"
       net use $drv /d | Out-Null
     }
     Write-Verbose "Ending $($MyInvocation.Mycommand)"
 } #end
} #end function Copy-Item2


This command is essentially the same as the previous version. The only thing that differs is how the temporary drive is created. In my testing, the NET USE approach was much more resilient. Although if you have an existing drive mapping to the server you will still get the multiple connections not allowed error. But at least in my experience this has been easier to troubleshoot and resolve.
Proxy functions can be great problem solvers because you can take advantage of all the cmdlet’s features with the benefit of adding your own customizations. I expect will see proxy functions solving future PowerShell problems.