Powershell: Enumerate and copy files from a network share
In Powershell 2.0 there is no real good way to copy files from a network share using alternate credentials. Since I have need to do this from time to time I wrote a couple of powershell functions which wrap around Wscript.Network. This helps keep (most) all of my Windows scripting inside Powershell.
References:
- Connecting to a network folder with username/password in Powershell [stackoverflow.com]
- copy-item With Alternate Credentials [stackoverflow.com]
- Wscript.network and Get-cred [tek-tips.com]
- Bug: FileSystem Provider should support credentials [connect.microsoft.com]
- Wscript.Network [blogs.technet.com]
- Copy Files with Alternate User Credentials [poshtips.com]
Enumerate Files / folders in a network share
I divided my network copy functions into 2: one to enumerate, the other to perform the actual copy. This works for me since the folder that I need to copy FROM is a directory with a time-formatted name. I need to make sure I copy the newest item, so I use this method to enumerate the directories then use other code to sort them and grab the latest one for copying. So far I have found them to work reliably on Windows Server 2008 R2 systems (seems to fail on Windows 7 boxes). This method is a wrapper around the Wscript.Network object which:
- Accepts the following parameters:
- Share location
- Source location (relative to share root)
- FoldersOnly (in case you only want to get a list of directories)
- Username
- Password
- The method contains an override to a default username and password which can and should be removed for production code. These functions run inside my test lab
- Maps a V: drive using Wscript.Network
- Drive letter can be altered inline without issue. "V" is an unpopulare driver letter around here
- Returns an array of what is found at the target location. If FoldersOnly is specified then only directories will be returned
Function (Multi-line comments have space before and after the hash (#) symbol so concrete5 doesn't filter them out):
< # UNCEnumerate will list all the Files and Folders from a UNC location
using the given Username and Password
- If no username/password is specified this will default to
"AuthorizedUser" and "AuthorizedPassword"
- If $foldersOnly is set to $true this function will only return
the FullNames of any directories.
.. Otherwise the Names of all items will be returned
(This is different than FULLNAME)
- $sourceLoc should be relative to $share
(for example if $share == "\\backup\share\" then $sourceLoc could
be "directory\subdirectory"
.. This example is valid if you have want to
access \\backup\share\directory\subdirectory
# >
function UNCEnumerate($share, $sourceLoc, $foldersOnly, $username, $password){
# -- Setup default username / password if one is not provided
if($username.Length -lt 1){
$username = "AuthorizedUser"
}
if($password.Length -lt 1){
$password = "AuthorizedPassword"
}
# -- Map Network drive using specified credentials
$net = new-object -ComObject Wscript.Network
Write-Host $share
Write-Host "net var before mapping: " + $net
$net.MapNetworkDrive("v:", $share, $false, $username, $password)
Write-Host "net var after mapping: " + $net
# -- Enumerate Files and Folders
$mappedSource = "V:\" + $sourceLoc
$childItems = Get-ChildItem $mappedSource
$results = @()
# -- Filters for folders (If specified)
foreach($childItem in $childItems){
if($foldersOnly){
if ($childItem.PSIsContainer){
$results += $childItem.Name
}
}
else{
$results += $childItem.Name
}
}
# -- Disconnect the mapped network drive
$net.RemoveNetworkDrive("v:")
return $results
}
Copy files from a network share
As mentioned above I use the Enumerate method to find the latest date-time stamped directory that I want to copy from. I take the path to that folder and pass it into this UNCCopy function which pulls the contents across the network using the specified credentials. Like the previous method I've baked-in some default credentials (not a good idea in a production environment) which can be removed or edited for your convenience.
Here is what the UNCCopy method does:
- Accepts the following arguments:
- Share
- SourceLocation (relative to the share)
- DestinationLocation (Local destination)
- Recursive
- Username
- Password
- Pass in a $null to use the default specified in the method
- Uses Wscript.Network to map a "V" drive
- Can be altered to use any other drive letter
- Copies the files per the passed-in arguments
- If Recursive is set to $true it will recursively copy the contents of folders
Function:
# UNCCopy will copy a file from an absolute UNC Source <br /> location to the specified destination folder <br />- When $recursive == $true, this function will recursively copy <br /> everything from the $sourceLoc to $destinationLoc <br />- $sourceLoc should be relative to $share <br />- If a $username and $password are not specified, default <br /> values of "Username" and "MagicPassword" will be used <br /># ></strong></span>
function <strong>UNCCopy</strong>($share, $sourceLoc, $destinationLoc, $recursive, $username, $password){
# -- Setup default username / password if one is not provided
if($username.Length -lt 1){
$username = "Username"
}
if($password.Length -lt 1){
$password = "MagicPassword"
}
# -- Map Network drive using specified credentials
$net = new-object -ComObject Wscript.Network
$net.MapNetworkDrive("v:", $share, $false, $username, $password)
# -- Perform the copy operation
$sourcePath = "V:\" + $sourceLoc
if($recursive){
Copy-Item $sourcePath\* $destinationLoc -Recurse
}
else{
Copy-Item $sourcePath $destinationLoc
}
# -- Disconnect the mapped network drive
$net.RemoveNetworkDrive("v:")
}
Sample Usage
Should be relatively straight-forward. This example will:
- Find the newest date-time labelled Directory from a network share using specified credentials
- Copy the files to a location specified in the $transferDest variable using function default credentials
# Set base variables
$BuildShare = "\\10.11.12.13\g$"
$sourceLoc = "builds\product\branch"
$username = "AuthorizedUser"
$password = "SuperSecretPassord"
$transferDest = "C:\Where\I\Want\To\Copy\To"
# Find the newest date-time folder (Simple, breaks when directories have more than numbers in them)
$dirs = UNCEnumerate $BuildShare $sourceLoc $true $username $password
$dirs = $dirs | sort
$transferLoc = $dirs[$dirs.Length - 1]
# Perform the Recursive copy
UNCCopy $BuildShare ($transferLoc + "\Binaries") ($transferDest+"\Bins") $true $null $null
Troubleshooting / Final notes
Issue 1: If you are like me and bounce back and forth between a number of environments each day it can be difficult to remember how to concatenate strings sometimes. In Powershell I've tried to use the Bash method (and vice-versa). Sometimes I get messages like this:
Copy-Item : Cannot find path 'V:\backup\Test\path\0302204-53444+\Binaries' because it does not exist.
At C:\Installer.ps1:66 char:12
+ Copy-Item <<<< $sourcePath\* $destinationLoc -Recurse
+ CategoryInfo : ObjectNotFound: (V:\backup\Test\...53444+\Binaries:String) [Copy-Item], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.CopyItemCommand
In this example message you can see paths with a the plus sign in them where you are not expecting it. you can eliminate this by using Parenthesis to group concatenations. For example (Parenthesis-grouped parameters have been bolded):
UNCCopy $BuildShare ($transferLoc + "\Binaries") ($transferLoc +"\Bins") $true $null $null
When you concatenate & then group like this there is no ambiguity as to what consitutes the parameter value. Function calling in powershell can be frustrating since it is kind of like JavaScript but without the nice C-Style function invokation syntax
Issue 2: If you see an error like this when using the DNS Name, you will want to try using an alternate name (cname) or an IP Address. I wasn't able to figure out why I kept receiving this error when ALL of my shares were unmounted and my machine rebooted, so i just used the IP Address work-around:
System error 1219 has occurred.
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.
Final (Off-topic) Note:
This doesn't really 'fit' here but I want to comment on it without making a whole 'article' or post about it. In Powershell if you have a GUI application (like a WinForms) app that you want to script in-line in a powershell script you need to do something special to keep it from executing asynchronously (which is the default).
To execute a scriptable GUI application in synchronous mode you can pipe-out the output from the exe and save it or dump it somewhere like this:
& "c:\program files\executable.exe "-arg1 -arg2 -arg3" | out-null
This is relevant to my scripts since we have many GUI applications which can be scripted at the command line that need to be executed in the proper order (not async).