Thursday, February 26, 2009

More PowerShell Remoting

I have spent some time on forums inquiring about the ability to transfer files between machines using PowerShell Remoting. I have an inkling I've been asking the wrong question. Until I have PowerShell Remoting configured correctly I'm not going to be able to do any useful experimentation.


Now, there are some breaking changes to PowerShell in v2 CTP3. For example Invoke-Expression no longer has a ComputerName parameter; this is now the domain of Invoke-Command. It makes searching for useful information on Remoting a little more interesting. So lets have a look at the result of issuing a remote Get-Process:



PS C:\Windows\System32> invoke-command -computername w2k8r2 {get-process}
Invoke-Command : [w2k8r2] Connecting to remote server failed with the following error message : The WinRM client cannot process
the request. If the authentication scheme is different from Kerberos, or if the client computer is not joined to a domain, then
HTTPS transport must be used or the destination machine must be added to the TrustedHosts configuration setting. Use
winrm.cmd to configure TrustedHosts. You can get more information about that by running the following command: winrm help config.
At line:1 char:15
+ invoke-command <<<< -computername w2k8r2 {get-process}
+ CategoryInfo : OpenError: (:) [Invoke-Command], PSRemotingTransportException
+ FullyQualifiedErrorId : RemoteRunspaceStateBroken

Fair enough, neither the client nor the server is part of a domain. Adding the destination machine to the TrustedHosts configuration setting is generally accompanied by warnings not to use this option for a production environment. That leaves me the option of using HTTPS and that requires a certificate. For my purposes a self-signed certificate is going to be perfectly adequate.


Server Activation


As an aside, I've not been able to activate my new server. Whenever I tried I would get error 0x80072EE7. The penny finally dropped - without a DNS server there is no way to identify the IP address of the Microsoft site used for activation, doh. So lets get that out of the way. The product key for the server is available on the Windows Server 2008 R2 Beta download page:



C:\Users\dave>netsh interface ipv4 add dnsservers "Local Area Connection" 10.20.1.1
The service has not been started.
PS C:\Users\dave> set-service dnscache -startupType automatic
PS C:\Users\dave> start-service dnscache
PS C:\Users\dave> cscript c:\windows\system32\slmgr.vbs -ipk FH4Y6-XGKH9-V6W22-YB7YT-HQDFB
Microsoft (R) Windows Script Host Version 5.8
Copyright (C) Microsoft Corporation. All rights reserved.

Installed product key FH4Y6-XGKH9-V6W22-YB7YT-HQDFB successfully.

PS C:\Users\dave> cscript c:\windows\system32\slmgr.vbs -ato
Microsoft (R) Windows Script Host Version 5.8
Copyright (C) Microsoft Corporation. All rights reserved.

Activating Windows(R) Web Server, ServerWebCore edition (220cd791-4e60-4412-8fcf-d605f54d3ae0) ...
Product activated successfully.

PS C:\Users\dave> cscript c:\windows\system32\slmgr.vbs -xpr
Microsoft (R) Windows Script Host Version 5.8
Copyright (C) Microsoft Corporation. All rights reserved.

Windows(R) Web Server, ServerWebCore edition:
The machine is permanently activated.


MakeCert


Sweet, now back to the task at hand. I'm using information from Brian Ehlert's IT Proctology to configure WinRM with a self-signed certificate. I don't have a copy of SelfSSL.exe but I do have two copies of makecert.exe. Past experience tells me all makecert.exe's are not created equal and as usual the first one I try fails. So the version I'm using is C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\makecert.exe which I suspect was installed with Visual Studio. Attempting to run makecert on my server doesn't work:



PS E:\> .\makecert.exe
Program 'makecert.exe' failed to execute: The subsystem needed to support the image type is not present
At line:1 char:15
+ .\makecert.exe <<<< .
At line:1 char:1
+ <<<< .\makecert.exe
+ CategoryInfo : ResourceUnavailable: (:) [], ApplicationFailedException
+ FullyQualifiedErrorId : NativeCommandFailed

WoW64


Obviously I'm missing something. WoW64 support for 32 bit applications is now an optional feature in Server Core and isn't installed by default. I also have the 32-bit .Net Frameworks installed so I figure I might as well include support for that. And a restart is required.



C:\Users\dave>start /w ocsetup ServerCore-WOW64
C:\Users\dave>Start /w ocsetup NetFx2-ServerCore-WOW64
C:\Users\dave>Start /w ocsetup NetFx3-ServerCore-WOW64
C:\Users\dave>shutdown /r /t 0

Using PowerShell to eject CD/DVD


Another short aside, the Mac Mini I'm using for my web server doesn't have an eject button. As I mentioned way back here it is possible to eject media by holding down the left mouse button during a restart. Even for a non-server environment this isn't particularly practical. Googling turned up a lot of suggestions around scripting the Windows media player, not an option for Server Core. I finally tracked down a workable solution (and useful explanation) on the vistax64 PowerShell forum. Note in the script below there is no ampersand in the verb Eject. This is contrary to the forum post and had me scratching my head for a while - I could hear the drive being accessed but the DVD wasn't being ejected. Notice also the call to ReleaseComObject and Remove-Variable, a little bit of tidying up to ensure the Shell.Application COM object is disposed of and the associated object reference is deleted:



PS C:\Users\dave\Documents> $sa = new-object -com Shell.Application
PS C:\Users\dave\Documents> $sa.Namespace(17).ParseName("D:").InvokeVerb("Eject")
PS C:\Users\dave\Documents> [System.Runtime.Interopservices.Marshal]::ReleaseComObject($sa)
PS C:\Users\dave\Documents> Remove-Variable sa

Creating a self-signed certificate


So lets see about creating a certificate. I need to create one that can be used for Server Authentication and because it's not from an existing Trusted Certificate Authority I'm going to also install it into the Trusted Root Certification Authorities store using the command line utility certutil.exe.



PS C:\Users\dave> .\makecert.exe -r -pe -n "CN=W2k8r2,O=Zebra Crossing Ltd" -e 01/01/2036 -eku 1.3.6.1.5.5.7.3.1 -ss my
-sr localMachine -sky exchange -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 W2k8r2.cer
Succeeded

PS C:\Users\dave> certutil -store "my" "W2k8r2"
my
================ Certificate 2 ================
Serial Number: 18a1515e9f87bc8c4047985ca34ef87b
Issuer: CN=W2k8r2, O=Zebra Crossing Ltd
NotBefore: 27/02/2009 1:10 p.m.
NotAfter: 1/01/2036 12:00 a.m.
Subject: CN=W2k8r2, O=Zebra Crossing Ltd
Signature matches Public Key
Root Certificate: Subject matches Issuer
Cert Hash(sha1): 3d bf 69 dc 88 c1 bd 68 bc b0 dc 7c af 4c 9f 44 4a fb 78 73
Key Container = 6755b6e3-4b68-4444-8478-f418c9bf58c8
Unique container name: ec9335511231310c09e44db4db78a25b_2515a01f-d083-4c9d-abf6-5ef325a4f5e5
Provider = Microsoft RSA SChannel Cryptographic Provider
Encryption test passed
CertUtil: -store command completed successfully.

PS C:\Users\dave\Documents> certutil -addstore root .\W2k8r2.cer
root
Signature matches Public Key
Certificate "CN=W2k8r2, O=Zebra Crossing Ltd" added to store.
CertUtil: -addstore command completed successfully.

PS C:\Users\dave\Documents> certutil -store root W2k8r2
root
================ Certificate 0 ================
Serial Number: 5e4aae197bd461994867979c58549cff
Issuer: CN=W2k8r2, O=Zebra Crossing Ltd
NotBefore: 27/02/2009 1:37 p.m.
NotAfter: 1/01/2036 12:00 a.m.
Subject: CN=W2k8r2, O=Zebra Crossing Ltd
Signature matches Public Key
Root Certificate: Subject matches Issuer
Cert Hash(sha1): da bd de a7 72 a8 6d 0d 1f d4 4b b5 37 d9 12 c6 49 a9 56 0d
No key provider information
Encryption test passed
CertUtil: -store command completed successfully.

Configuring WinRM


The certificate hash from the previous step is the thumbprint required for setting up a new WinRM listener. Note in the next step we're not using PowerShell, just the standard command prompt. Trying to issue this same command in PowerShell gives Error: Invalid use of command line. Type "winrm -?" for help. I figure there must be some escaping needed to get it to work in PowerShell, something to look at another day.



C:\Users\dave>winrm create winrm/config/Listener?Address=*+Transport=HTTPS
@{Hostname="W2k8r2";CertificateThumbprint="dabddea772a86d0d1fd44bb537d912c649a9560d"}
ResourceCreated
Address = http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
ReferenceParameters
ResourceURI = http://schemas.microsoft.com/wbem/wsman/1/config/listener
SelectorSet
Selector: Address = *, Transport = HTTPS

Time to test the configuration on the server.



C:\Users\dave>winrs -r:https://W2k8r2 hostname
W2k8r2

On the client Invoke-Command can be used to make the same request. Of course the certificate on the server is not from a trusted Certificate Authority and the result is not pretty:



PS C:\Users\dave> invoke-command -useSSl W2k8r2 {hostname}
Invoke-Command : [W2k8r2] Connecting to remote server failed with the following error message : The server certificate on
the destination computer (W2k8r2:443) has the following errors:
The SSL certificate is signed by an unknown certificate authority.
At line:1 char:15
+ invoke-command <<<< -useSSl W2k8r2 {hostname}
+ CategoryInfo : OpenError: (:) [Invoke-Command], PSRemotingTransportException
+ FullyQualifiedErrorId : RemoteRunspaceStateBroken

Fortunately there is an option to ignore the Certificate Authority check:



PS C:\Users\dave> $so = New-WSManSessionOption -SkipCACheck
PS C:\Users\dave> invoke-command -useSSl -SessionOption $so W2k8r2 {hostname}

W2k8r2

Cool, now lets see if I can get a file transferred between the server and my Vista VM. I specify a -ReadCount argument of 0 to indicate the entire contents should be sent at one time (the default is 1 line and for a binary file is extremely slow):



PS C:\Users\dave> invoke-command -useSSL -SessionOption $so W2k8r2 {get-content -encoding byte -ReadCount 0
"C:/Users/dave/Documents/makecert.exe"} | set-content -encoding byte .\Documents\makecert.exe

PS C:\Users\dave> .\Documents\makecert.exe
Error: Please either specify the outputCertificateFile or -ss option
Usage: MakeCert [ basic|extended options] [outputCertificateFile]
Basic Options
-sk <keyName> Subject's key container name; To be created if not present
[rest of output elided]

Hooray. A little tweaking and I should be able to reliably transfer files over the WinRM connection to any path on the server that I have the authority to access.


Not so fast


Not quite there yet, turns out the only time I can successfully connect using PowerShell Remoting is when I'm also logged in using Remote Desktop. <curses foiled="again"/>

2 comments:

David Warburton said...

Good luck w/ this. I've been going back on forth on whether or not to tackle a project based on winrm and powershell. I think i'm going to table it. I didn't get any such "configure-wsman.ps1" script either. I suppose they took it out of ctp3 for v2.0.

Mark Stanfill said...

For anyone looking for this: Powershell 2.0 remoting makes this very easy---

$cred = Get-Credential

Invoke-Command -SessionOption {get-content -encoding byte -readcount 0 "C:\windows\system32\notepad.exe" } -Credential $cred |Set-Content -Encoding byte c:\temp\foooooooo.exe