# Configure a Windows host for remote management with Ansible # ----------------------------------------------------------- # # This script checks the current WinRM/PSRemoting configuration and makes the # necessary changes to allow Ansible to connect, authenticate and execute # PowerShell commands. # # Written by Trond Hindenes # Updated by Chris Church # Updated by Kurt Griffiths # Param ( [string]$SubjectName = $env:COMPUTERNAME, $CreateSelfSignedCert = $true ) Function New-LegacySelfSignedCert { Param ( [string]$SubjectName ) # New-SelfSignedCertificate is hard-coded for 1 year, so follow suit $ValidDays = 365 $name = New-Object -COM "X509Enrollment.CX500DistinguishedName.1" $name.Encode("CN=$SubjectName", 0) $key = New-Object -COM "X509Enrollment.CX509PrivateKey.1" $key.ProviderName = "Microsoft RSA SChannel Cryptographic Provider" $key.KeySpec = 1 $key.Length = 1024 $key.SecurityDescriptor = "D:PAI(A;;0xd01f01ff;;;SY)(A;;0xd01f01ff;;;BA)(A;;0x80120089;;;NS)" $key.MachineContext = 1 $key.Create() $serverauthoid = New-Object -COM "X509Enrollment.CObjectId.1" $serverauthoid.InitializeFromValue("1.3.6.1.5.5.7.3.1") $ekuoids = New-Object -COM "X509Enrollment.CObjectIds.1" $ekuoids.Add($serverauthoid) $ekuext = New-Object -COM "X509Enrollment.CX509ExtensionEnhancedKeyUsage.1" $ekuext.InitializeEncode($ekuoids) $cert = New-Object -COM "X509Enrollment.CX509CertificateRequestCertificate.1" $cert.InitializeFromPrivateKey(2, $key, "") $cert.Subject = $name $cert.Issuer = $cert.Subject $cert.NotBefore = (Get-Date).AddDays(-1) $cert.NotAfter = $cert.NotBefore.AddDays($ValidDays) $cert.X509Extensions.Add($ekuext) $cert.Encode() $enrollment = New-Object -COM "X509Enrollment.CX509Enrollment.1" $enrollment.InitializeFromRequest($cert) $certdata = $enrollment.CreateRequest(0) $enrollment.InstallResponse(2, $certdata, 0, "") # Return the thumbprint of the last installed cert. Get-ChildItem "Cert:\LocalMachine\my"| Sort-Object NotBefore -Descending | Select -First 1 | Select -Expand Thumbprint } # Setup error handling. Trap { $_ Exit 1 } $ErrorActionPreference = "Stop" # Find and start the WinRM service. Write "Verifying WinRM service." If (!(Get-Service "WinRM")) { Throw "Unable to find the WinRM service." } ElseIf ((Get-Service "WinRM").Status -ne "Running") { Write "Starting WinRM service." Start-Service -Name "WinRM" -ErrorAction Stop } Else { Write "WinRM service is running." } # WinRM should be running; check that we have a PS session config. If (!(Get-PSSessionConfiguration -Verbose:$false) -or (!(Get-ChildItem WSMan:\localhost\Listener))) { Write "Enabling PS Remoting." Enable-PSRemoting -Force -ErrorAction Stop } Else { Write "PS Remoting is already enabled." } # Make sure there is an SSL listener. $listeners = Get-ChildItem WSMan:\localhost\Listener If (!($listeners | Where {$_.Keys -like "TRANSPORT=HTTPS"})) { If (!$CreateSelfSignedCert) { Throw "SSL listener is not enabled. Manually enable the SSL listener and re-run this script." } # HTTPS-based endpoint does not exist. If (Get-Command "New-SelfSignedCertificate" -ErrorAction SilentlyContinue) { $cert = New-SelfSignedCertificate -DnsName $SubjectName -CertStoreLocation "Cert:\LocalMachine\My" $thumbprint = $cert.Thumbprint } Else { # w2k8 may not have New-SelfSignedCertificate so we have to roll our own $thumbprint = New-LegacySelfSignedCert -SubjectName $SubjectName } # Create the hashtables of settings to be used. $valueset = @{} $valueset.Add('Hostname', $SubjectName) $valueset.Add('CertificateThumbprint', $thumbprint) $selectorset = @{} $selectorset.Add('Transport', 'HTTPS') $selectorset.Add('Address', '*') Write "Enabling SSL listener." New-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorset -ValueSet $valueset } Else { Write "SSL listener is already active." } If (($listeners | Where {$_.Keys -like "TRANSPORT=HTTP"})) { Write "Warning: HTTP listener is enabled. Do not use basic auth on port 5985." # Write "Disabling plaintext listener before enabling basic auth to avoid opening an attack vector." # $selectorset = @{} # $selectorset.Add('Transport', 'HTTP') # $selectorset.Add('Address', '*') # Remove-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorset } # Check for basic authentication. $basicAuthSetting = Get-ChildItem WSMan:\localhost\Service\Auth | Where {$_.Name -eq "Basic"} If (($basicAuthSetting.Value) -eq $false) { Write "Enabling basic auth support." Set-Item -Path "WSMan:\localhost\Service\Auth\Basic" -Value $true } Else { Write "Basic auth is already enabled." } # Test a remoting connection to localhost, which should work. $httpsOptions = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck $httpsResult = New-PSSession -UseSSL -ComputerName "localhost" -SessionOption $httpsOptions -ErrorVariable httpsError -ErrorAction SilentlyContinue If ($httpsResult) { Write "HTTPS sessions are enabled." } Else { Throw "Unable to establish an HTTP or HTTPS remoting session." } # Configure firewall to allow WinRM HTTPS connections. We have to use netsh because # New-NetFirewallRule may not be available on w2k8. $rule_name = "Windows Remote Management (HTTPS-In)" $fwtest1 = netsh advfirewall firewall show rule name=$rule_name $fwtest2 = netsh advfirewall firewall show rule name=$rule_name profile=any If ($fwtest1.count -lt 5) { Write "Adding firewall rule to allow WinRM HTTPS." netsh advfirewall firewall add rule profile=any name=$rule_name dir=in localport=5986 protocol=TCP action=allow } ElseIf (($fwtest1.count -ge 5) -and ($fwtest2.count -lt 5)) { Write "Updating firewall rule to allow WinRM HTTPS for any profile." netsh advfirewall firewall set rule name=$rule_name new profile=any } Else { Write "Enabling existing firewall rule to allow WinRM HTTPS." netsh advfirewall firewall set rule name=$rule_name new enable=yes } Write "WinRM service has been successfully configured."