Monday, September 27, 2010

Powershell: Start-BitsTransfer & User Authentication

I recently discussed an issue with ActualReverend (alright it was a month and a half ago and I'm just now blogging about it) concerning Powershell and using Start-BitsTransfer against a web server requiring user authentication. Additionally, this is my first time ever using Powershell for any task more than running 'ipconfig /all'. So I suppose we'll start with our problem statement:
"Using 'Start-BitsTransfer', download a file from a web server (an apache/svn service), which requires user authentication against an LDAP server, to a given location. Path is '/path/file.ps1' user credentials are 'username:password.' "
Should be a fairly simple script, he needed it for more but I am mostly interested with why it's not working as expected.
$creds = Get-Credential
Start-Bitstransfer -Source -Destination C:\destination\ -Credential $creds -Authentication basic
Pretty straight forward script. It prompts the user for their credentials we enter "username" and "password" as that's our credentials. The authentication type is basic, and the server is properly configured to goto LDAP, so we're ready to Rock 'N Roll... But the results are of course not as expected, let's see the error.
Start-BitsTransfer : HTTP status 401: The requested resource requires user authentication.
Well, damn. We thought that providing the username and password pair would cover us but something went wrong and the error is not as forthcoming with the cause as we'd like. We provided credentials and the fact that we wanted the cmdlet to use authentication, so what the hell? Maybe our server logs will provide some insight.
[Thu Aug 19 13:30:50 2010] [error] [client] user \\username not found: /path/file.ps1 
 Ah ha, so our user isn't found. Thats weird though why is there a '\\' preceding our username, we didn't enter that! Lets inspect our object $creds and see if it supports this outrageous server log claim (hopefully identify the source of the hiccup).
PS C:\Users\username>$creds 
UserName              Password
--------              --------
\username             System.Security.SecureString
PS C:\Users\username>
Huh so, there is our issue. Our LDAP server certainly has no idea who '\username' is. So our Username property in our... wait what type of object is this? Running $creds.GetType() shows us its a 'PSCredential' type. The MSDN shows us this is in the 'System.Management.Automation' namespace. So in theory I can use the New-Object cmdlet to make a new object with the contents of the old object and clean it up, right? Right!
PS C:\Users\username> $creds = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList $creds.Username.Remove(0,1),$creds.Password
Alright,  lets check out our object now...
PS C:\Users\username>$creds 
UserName              Password
--------              --------
username             System.Security.SecureString
PS C:\Users\username>

Very cool, so lets try it out (cross our fingers):

PS C:\Users\username> Start-BitsTransfer -Credential $creds -Source "" -Destination C:\destination\ -Authentication basic
PS C:\Users\username> 
VoilĂ , we're now providing our user credentials successfully and downloading the file we want without error! Powershell, you have just made my Windows using experience that much better.


  1. Cool writeup. The reason you are getting the backslash in the username field is because the Get-Credentials Cmdlet is expecting you to provide a windows domain or computer realm along with the username. From the technet article "If you enter a user name without a domain, Get-Credential inserts a backslash before the name." On a windows network whenever you are provided with a username/password that doesn't have an input field for domain name you should always pass "DOMAIN\USERNAME". Or you can just use your domain account UPN in most cases as the username. Supplying the proper username format avoids the extra coding.

    1. You are absolutely correct as to why the '\' is added. And in a Microsoft only based network I would agree that you should always use "DOMAIN\USERNAME".

      This is not a Microsoft only based network. And the use of domains is not ubiquitous. Therefore, a more useful solution, would be to genericize the Get-Credentials cmdlet by removing the assumed logic of prepending a '\' - or optionally a switch to disable that logic. There are a million servers (or more) in the world which will not be able to authenticate or authorize a username with a preceding '\'.