$ErrorActionPreference = 'Stop' Write-Host 'Configuring WinRM for Ansible...' # --------------------------------------------------------------------------- # Configuration # --------------------------------------------------------------------------- $certificateFriendlyName = 'WinRM' $firewallRuleName = 'Windows Remote Management (HTTPS-In)' # Optional additional DNS names or IP addresses, e.g. NAT / bitconnect addresses. $additionalSANs = @( # '192.168.100.10' # 'server.example.lan' ) # --------------------------------------------------------------------------- # Enable WinRM # --------------------------------------------------------------------------- Write-Host 'Enabling WinRM...' winrm quickconfig -quiet Set-Service -Name WinRM -StartupType Automatic if ((Get-Service -Name WinRM).Status -ne 'Running') { Start-Service -Name WinRM } # --------------------------------------------------------------------------- # Determine host names and IP addresses # --------------------------------------------------------------------------- $hostName = $env:COMPUTERNAME $computerSystem = Get-CimInstance Win32_ComputerSystem $domain = $computerSystem.Domain $hostIPs = @( Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue | Where-Object { $_.IPAddress -notlike '127.*' -and $_.IPAddress -notlike '169.254.*' } | Select-Object -ExpandProperty IPAddress -Unique ) if (-not $hostIPs) { throw 'No usable IPv4 address found for WinRM certificate creation.' } $certificateNames = @($hostName) if ($computerSystem.PartOfDomain -and -not [string]::IsNullOrWhiteSpace($domain)) { $certificateNames += "$hostName.$domain" } $certificateNames += $hostIPs $certificateNames += $additionalSANs $certificateNames = @( $certificateNames | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique ) Write-Host 'Certificate SANs:' $certificateNames | ForEach-Object { Write-Host " - $_" } # --------------------------------------------------------------------------- # Find or create WinRM certificate # --------------------------------------------------------------------------- $cert = Get-ChildItem 'Cert:\LocalMachine\My' | Where-Object { $_.FriendlyName -eq $certificateFriendlyName -and $_.NotAfter -gt (Get-Date).AddDays(30) } | Sort-Object NotAfter -Descending | Select-Object -First 1 if (-not $cert) { Write-Host 'Creating self-signed WinRM certificate...' $certCommand = Get-Command New-SelfSignedCertificate -ErrorAction Stop $certParams = @{ DnsName = $certificateNames } if ($certCommand.Parameters.ContainsKey('CertStoreLocation')) { $certParams['CertStoreLocation'] = 'Cert:\LocalMachine\My' } if ($certCommand.Parameters.ContainsKey('FriendlyName')) { $certParams['FriendlyName'] = $certificateFriendlyName } if ($certCommand.Parameters.ContainsKey('TextExtension')) { $certParams['TextExtension'] = '2.5.29.37={text}1.3.6.1.5.5.7.3.1' } try { $cert = New-SelfSignedCertificate @certParams } catch { if ($_.CategoryInfo.Reason -ne 'InvalidStorePathException' -or -not $certParams.ContainsKey('CertStoreLocation')) { throw } # Windows Server 2012 R2 / PowerShell 4 can expose CertStoreLocation but # reject the valid Cert:\LocalMachine\My argument. The compatibility # path is to run the cmdlet while the certificate provider is in that store. Write-Host 'Legacy certificate-store behavior detected; retrying in compatibility mode.' $certParams.Remove('CertStoreLocation') Push-Location 'Cert:\LocalMachine\My' try { $cert = New-SelfSignedCertificate @certParams } finally { Pop-Location } } if ($cert.FriendlyName -ne $certificateFriendlyName) { try { $cert.FriendlyName = $certificateFriendlyName } catch { Write-Warning 'Certificate was created, but its friendly name could not be set. Future runs may create a replacement certificate.' } } } else { Write-Host 'Existing WinRM certificate found.' } if (-not $cert.Thumbprint) { throw 'WinRM certificate does not contain a valid thumbprint.' } Write-Host "Certificate thumbprint: $($cert.Thumbprint)" # --------------------------------------------------------------------------- # Configure HTTPS listener # --------------------------------------------------------------------------- $httpsListener = Get-ChildItem WSMan:\localhost\Listener | Where-Object { $_.Keys -contains 'Transport=HTTPS' } | Select-Object -First 1 if (-not $httpsListener) { Write-Host 'Creating WinRM HTTPS listener...' New-Item -Path WSMan:\localhost\Listener -Transport HTTPS -Address * -CertificateThumbPrint $cert.Thumbprint -Force | Out-Null } else { Write-Host 'WinRM HTTPS listener already exists.' } # --------------------------------------------------------------------------- # Configure firewall # --------------------------------------------------------------------------- $firewallRule = Get-NetFirewallRule -DisplayName $firewallRuleName -ErrorAction SilentlyContinue if (-not $firewallRule) { Write-Host 'Creating WinRM HTTPS firewall rule...' New-NetFirewallRule -DisplayName $firewallRuleName -Direction Inbound -Protocol TCP -LocalPort 5986 -Action Allow -Program System | Out-Null } else { Write-Host 'WinRM HTTPS firewall rule already exists.' Enable-NetFirewallRule -DisplayName $firewallRuleName | Out-Null } # --------------------------------------------------------------------------- # Non-domain systems # --------------------------------------------------------------------------- if (-not $computerSystem.PartOfDomain) { Write-Host 'Non-domain system detected.' Write-Host 'Enabling remote administrative token for local accounts...' New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' -Name 'LocalAccountTokenFilterPolicy' -PropertyType DWord -Value 1 -Force | Out-Null } # --------------------------------------------------------------------------- # Verification # --------------------------------------------------------------------------- Write-Host '' Write-Host 'Verifying WinRM configuration...' $listener = Get-ChildItem WSMan:\localhost\Listener | Where-Object { $_.Keys -contains 'Transport=HTTPS' } | Select-Object -First 1 if (-not $listener) { throw 'WinRM HTTPS listener verification failed.' } $firewallRule = Get-NetFirewallRule -DisplayName $firewallRuleName -ErrorAction SilentlyContinue if (-not $firewallRule) { throw 'WinRM firewall rule verification failed.' } if ((Get-Service WinRM).Status -ne 'Running') { throw 'WinRM service is not running.' } Write-Host '' Write-Host 'WinRM configuration completed successfully.' Write-Host 'HTTPS port: 5986' Write-Host "Certificate: $($cert.Thumbprint)"