# Скрипт: winrm.ps1 # Версия: 3.3.1 # Дата: 2026-05-03 # Описание: Настройка WinRM — HTTP (5985) + HTTPS (5986) + сервисный аккаунт. # Поддержка Win7+PS2 и Win10+PS5. Идемпотентный. # # Запуск: # irm https://ps.gper.ru/winrm.ps1 | iex # # С параметрами (для автоматизации): <# $env:WINRM_USER = "tarakan" $env:WINRM_PASS = "17101983Dn*" irm https://ps.gper.ru/winrm.ps1 | iex #> # ============================================================ # КОДИРОВКА # ============================================================ # UTF-8 только на Win10+ (build 10240+). Win7 console subsystem не поддерживает UTF-8 # ни через chcp 65001, ни через [Console]::OutputEncoding — крашит с "Win32 internal error 0x1F". # На Win7 оставляем CP866 (OEM) — кириллица работает нативно. $_osBuild = [Environment]::OSVersion.Version.Build if ($_osBuild -ge 10240 -and $PSVersionTable.PSVersion.Major -ge 5) { try { [Console]::OutputEncoding = [Text.Encoding]::UTF8 $OutputEncoding = [Text.Encoding]::UTF8 } catch {} } # ============================================================ # КОНФИГУРАЦИЯ # ============================================================ $CREDS_DIR = "C:\ProgramData\WinRM" $CREDS_FILE = "$CREDS_DIR\credentials.json" $LOG_DIR = "$CREDS_DIR\logs" $LOG_FILE = "$LOG_DIR\winrm_$(Get-Date -Format 'yyyyMMdd').log" $WAIT_SEC = 3 $REG_HIDDEN = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList" # ============================================================ # ЛОГИРОВАНИЕ # ============================================================ function Write-Log { param( [ValidateSet('INFO','OK','WARN','ERROR')] [string]$Level, [string]$Message, [int]$WaitTimeOut = 0 ) $ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $entry = "[$ts] [$Level] $Message" if (!(Test-Path $LOG_DIR)) { New-Item -Path $LOG_DIR -ItemType Directory -Force | Out-Null } Add-Content -Path $LOG_FILE -Value $entry $color = switch ($Level) { 'OK' { 'Green' } 'WARN' { 'Yellow' } 'ERROR' { 'Red' } default { 'Cyan' } } Write-Host $entry -ForegroundColor $color if ($WaitTimeOut -ne 0) { $WaitColor = "Dark${color}" Write-Host "Ожидание $WaitTimeOut сек..." -ForegroundColor $WaitColor Start-Sleep -Seconds $WaitTimeOut } } # Тестирование # Write-Log -Level INFO -Message "Тестовое сообщение" # ============================================================ # ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ # ============================================================ # Имя группы Администраторов по SID (не зависит от локали) function Get-AdminGroupName { $sid = "S-1-5-32-544" if (Get-Command 'Get-LocalGroup' -ErrorAction SilentlyContinue) { $g = Get-LocalGroup | Where-Object { $_.SID.Value -eq $sid } if ($g) { return $g.Name } } # Fallback for PS2-4: CIM/WMI $g = Get-CimInstance -Class Win32_Group -Filter "SID='$sid' AND LocalAccount=True" -ErrorAction SilentlyContinue if ($g) { return $g.Name } return "Administrators" } # Скрыть пользователя с экрана входа function Hide-UserFromLogin { param([string]$UserName) if (!(Test-Path $REG_HIDDEN)) { New-Item -Path $REG_HIDDEN -Force | Out-Null } Set-ItemProperty -Path $REG_HIDDEN -Name $UserName -Value 0 -Type DWord -Force } # Показать пользователя на экране входа (убрать скрытие) function Show-UserAtLogin { param([string]$UserName) if (Test-Path $REG_HIDDEN) { Remove-ItemProperty -Path $REG_HIDDEN -Name $UserName -ErrorAction SilentlyContinue } } # Проверить, скрыт ли пользователь function Test-UserHiddenFromLogin { param([string]$UserName) if (!(Test-Path $REG_HIDDEN)) { return $false } $val = Get-ItemProperty -Path $REG_HIDDEN -Name $UserName -ErrorAction SilentlyContinue return $null -ne $val } # Создать/обновить WinRM-пользователя (PS5+) function Set-LocalWinRMUser { param([string]$UserName, [SecureString]$SecurePass) $existing = Get-LocalUser -Name $UserName -ErrorAction SilentlyContinue if ($existing) { Set-LocalUser -Name $UserName -Password $SecurePass -PasswordNeverExpires $true -AccountNeverExpires $true Write-Log "INFO" "Пользователь '$UserName': пароль обновлён" } else { New-LocalUser -Name $UserName -Password $SecurePass ` -FullName "WinRM Admin" -Description "WinRM remote management" ` -PasswordNeverExpires -AccountNeverExpires | Out-Null Write-Log "OK" "Пользователь '$UserName' создан" } $grp = Get-AdminGroupName $isMember = Get-LocalGroupMember -Group $grp -ErrorAction SilentlyContinue | Where-Object { $_.Name -like "*$UserName" } if (!$isMember) { Add-LocalGroupMember -Group $grp -Member $UserName Write-Log "OK" "Пользователь добавлен в группу '$grp'" } else { Write-Log "INFO" "Пользователь уже в группе '$grp'" } } # Настройка WinRM: HTTP + HTTPS function Configure-WinRM { param([string]$CertThumbprint) Set-Service WinRM -StartupType Automatic $svc = Get-Service WinRM if ($svc.Status -ne 'Running') { Start-Service WinRM } # Сетевые профили Public -> Private (нужно для quickconfig) if (Get-Command 'Get-NetConnectionProfile' -ErrorAction SilentlyContinue) { $pubProfiles = Get-NetConnectionProfile | Where-Object { $_.NetworkCategory -eq 'Public' } if ($pubProfiles) { $pubProfiles | Set-NetConnectionProfile -NetworkCategory Private Write-Log "OK" "Сетевые профили: Public -> Private" } } $out = cmd /c "winrm quickconfig -quiet -force 2>&1" Write-Log "INFO" "winrm quickconfig: выполнено" cmd /c 'winrm set winrm/config/service/auth @{Basic="true"}' | Out-Null cmd /c 'winrm set winrm/config/client/auth @{Basic="true"}' | Out-Null cmd /c 'winrm set winrm/config/service @{AllowUnencrypted="true"}' | Out-Null Write-Log "OK" "WinRM: Basic auth + AllowUnencrypted включены" # HTTPS-слушатель (порт 5986) — Basic auth работает по HTTPS на Win11 if ($CertThumbprint) { $httpsExists = winrm enumerate winrm/config/listener 2>&1 | Select-String 'HTTPS' -Quiet if (!$httpsExists) { $out = winrm create winrm/config/listener?Address=*+Transport=HTTPS "@{Hostname=`"$env:COMPUTERNAME`";CertificateThumbprint=`"$CertThumbprint`"}" 2>&1 if ($LASTEXITCODE -eq 0) { Write-Log "OK" "HTTPS-слушатель создан (порт 5986)" } else { Write-Log "WARN" "Ошибка создания HTTPS-слушателя: $out" } } else { Write-Log "INFO" "HTTPS-слушатель уже существует" } } # LocalAccountTokenFilterPolicy — обязательно для локальных аккаунтов Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" ` -Name "LocalAccountTokenFilterPolicy" -Value 1 -Type DWord -Force Write-Log "OK" "LocalAccountTokenFilterPolicy: включено" try { Set-Item WSMan:\localhost\Client\TrustedHosts -Value "*" -Force Write-Log "OK" "WinRM: TrustedHosts = *" } catch { Write-Log "WARN" "TrustedHosts: $($_.Exception.Message)" } } # Создать самоподписанный сертификат для WinRM HTTPS function New-WinRMCert { if (-not (Get-Command 'New-SelfSignedCertificate' -ErrorAction SilentlyContinue)) { Write-Log "WARN" "New-SelfSignedCertificate недоступен (PS < 5) — HTTPS пропущен" return $null } $existing = Get-ChildItem Cert:\LocalMachine\My | Where-Object { $_.Subject -eq "CN=$env:COMPUTERNAME" -and $_.NotAfter -gt (Get-Date).AddDays(30) } if ($existing) { Write-Log "INFO" "Сертификат WinRM уже существует: $($existing.Thumbprint)" return $existing.Thumbprint } $cert = New-SelfSignedCertificate ` -DnsName $env:COMPUTERNAME ` -CertStoreLocation Cert:\LocalMachine\My ` -KeyAlgorithm RSA -KeyLength 2048 ` -NotAfter (Get-Date).AddYears(10) ` -FriendlyName "WinRM HTTPS" Write-Log "OK" "Самоподписанный сертификат создан: $($cert.Thumbprint)" return $cert.Thumbprint } # Configure firewall rule TCP 5985 function Set-WinRMFirewall { $ruleName = "WinRM-HTTP-In" if (Get-Command 'Get-NetFirewallRule' -ErrorAction SilentlyContinue) { $existing = Get-NetFirewallRule -Name $ruleName -ErrorAction SilentlyContinue if ($existing) { Write-Log "INFO" "Firewall rule '$ruleName' already exists" return } New-NetFirewallRule -Name $ruleName -DisplayName "WinRM HTTP (gper)" ` -Protocol TCP -LocalPort 5985 -Action Allow -Direction Inbound -Enabled True | Out-Null Write-Log "OK" "Firewall rule created (TCP 5985)" } else { # Fallback: netsh (Win7/PS2) $out = cmd /c "netsh advfirewall firewall show rule name=""$ruleName"" 2>&1" if ($LASTEXITCODE -ne 0) { cmd /c "netsh advfirewall firewall add rule name=""$ruleName"" dir=in action=allow protocol=TCP localport=5985" | Out-Null Write-Log "OK" "Firewall rule created via netsh (TCP 5985)" } else { Write-Log "INFO" "Firewall rule already exists (netsh)" } } } # Сохранить информацию о конфигурации function Save-CredentialsInfo { param([string]$UserName, [string]$CertThumbprint) if (!(Test-Path $CREDS_DIR)) { New-Item -Path $CREDS_DIR -ItemType Directory -Force | Out-Null } $ip = $null if (Get-Command 'Get-NetIPAddress' -ErrorAction SilentlyContinue) { $ip = (Get-NetIPAddress -AddressFamily IPv4 | Where-Object { $_.IPAddress -ne "127.0.0.1" -and $_.PrefixOrigin -ne "WellKnown" } | Select-Object -First 1).IPAddress } if (!$ip) { $ip = (Get-CimInstance Win32_NetworkAdapterConfiguration | Where-Object { $_.IPEnabled -eq $true -and $_.IPAddress -ne $null } | Select-Object -First 1).IPAddress[0] } $data = @{ computer_name = $env:COMPUTERNAME ip_address = $ip winrm_user = $UserName winrm_port_http = 5985 winrm_port_https = 5986 cert_thumbprint = $CertThumbprint os_version = (Get-CimInstance Win32_OperatingSystem).Caption ps_version = $PSVersionTable.PSVersion.ToString() configured_at = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") } $data | ConvertTo-Json | Set-Content -Path $CREDS_FILE -Encoding UTF8 Write-Log "OK" "Информация сохранена: $CREDS_FILE" } # ============================================================ # ГЛАВНЫЙ БЛОК # ============================================================ Write-Host "" Write-Host "==========================================" -ForegroundColor Cyan Write-Host " Настройка WinRM" -ForegroundColor Cyan Write-Host " $env:COMPUTERNAME | PS $($PSVersionTable.PSVersion.Major)" -ForegroundColor Cyan Write-Host "==========================================" -ForegroundColor Cyan Write-Host "" Write-Log "INFO" "========== Настройка WinRM начата ==========" Write-Log "INFO" "ПК: $env:COMPUTERNAME | Пользователь: $env:USERNAME | PS: $($PSVersionTable.PSVersion)" # Admin check — self-elevate если нет прав $principal = New-Object Security.Principal.WindowsPrincipal( [Security.Principal.WindowsIdentity]::GetCurrent() ) if (!$principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { Write-Host "" Write-Host " Нет прав администратора. Перезапуск с повышением..." -ForegroundColor Yellow # Если запущен через irm | iex — нет файла скрипта, нельзя elevate # Если через Invoke-Command (WinRM) — $PSSenderInfo, elevate невозможен if ($PSSenderInfo -or !$PSCommandPath) { Write-Log "ERROR" "Нет прав администратора (elevate невозможен в этом режиме)" Write-Host " Запустите PowerShell от имени администратора и повторите." -ForegroundColor Red Write-Host " Нажмите любую клавишу..." -ForegroundColor Gray try { $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') } catch {} exit 1 } # Self-elevate: перезапуск скрипта с -Verb RunAs try { $argList = "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" Start-Process powershell.exe -Verb RunAs -ArgumentList $argList exit 0 } catch { Write-Log "ERROR" "Не удалось запустить с повышением: $($_.Exception.Message)" Write-Host " Запустите PowerShell от имени администратора и повторите." -ForegroundColor Red Write-Host " Нажмите любую клавишу..." -ForegroundColor Gray try { $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') } catch {} exit 1 } } Write-Log "OK" "Права администратора подтверждены" # Учётные данные WinRM-пользователя $wmUser = if ($env:WINRM_USER) { $env:WINRM_USER } else { "aadmin" } if ($env:WINRM_PASS) { $wmPass = ConvertTo-SecureString $env:WINRM_PASS -AsPlainText -Force Write-Log "INFO" "Учётные данные из переменных окружения" } else { Write-Host "" Write-Host "WinRM user credentials:" -ForegroundColor Yellow $inputUser = Read-Host " Логин [$wmUser]" if ($inputUser) { $wmUser = $inputUser } $wmPass = Read-Host " Пароль" -AsSecureString Write-Log "INFO" "Учётные данные введены вручную" } # [1/6] Создание пользователя Write-Host "" Write-Host "[1/6] Создание пользователя '$wmUser'..." -ForegroundColor Yellow $hasLocalUserCmdlets = $null -ne (Get-Command 'Get-LocalUser' -ErrorAction SilentlyContinue) if ($hasLocalUserCmdlets) { Set-LocalWinRMUser -UserName $wmUser -SecurePass $wmPass } else { # Win7/PS2-4 path Write-Log "WARN" "Get-LocalUser недоступен — используем net user / WMI" $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($wmPass) $plainPass = [Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr) [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) $userExists = Get-CimInstance -Class Win32_UserAccount ` -Filter "Name='$wmUser' AND LocalAccount=True" -ErrorAction SilentlyContinue if ($userExists) { net user $wmUser $plainPass 2>&1 | Out-Null Write-Log "INFO" "Пользователь '$wmUser': пароль обновлён (net user)" } else { $out = net user $wmUser $plainPass /add /comment:"WinRM Admin" /expires:never /passwordchg:no 2>&1 if ($LASTEXITCODE -eq 0) { wmic useraccount where "name='$wmUser'" set PasswordExpires=False 2>&1 | Out-Null Write-Log "OK" "Пользователь '$wmUser' создан (net user)" } else { Write-Log "ERROR" "Не удалось создать пользователя: $out" Remove-Variable plainPass -Force exit 1 } } Remove-Variable plainPass -Force $grp = Get-AdminGroupName $memberCheck = net localgroup "$grp" 2>&1 | Select-String $wmUser if (!$memberCheck) { net localgroup "$grp" $wmUser /add 2>&1 | Out-Null Write-Log "OK" "Пользователь добавлен в группу '$grp' (net localgroup)" } else { Write-Log "INFO" "Пользователь уже в группе '$grp'" } } # [2/6] Скрытие из экрана входа Write-Host "" Write-Host "[2/6] Скрытие из экрана входа..." -ForegroundColor Yellow if ($env:USERNAME -eq $env:WINRM_HIDEUSER) { Write-Log "WARN" "Текущий залогиненный пользователь 'UserName = $($env:USERNAME)' не может быть скрыт с экрана входа" -WaitTimeOut 3 } else { $isAlreadyHidden = Test-UserHiddenFromLogin -UserName $wmUser $doHide = $null if ($null -ne $env:WINRM_HIDEUSER ) { $doHide = $env:WINRM_HIDEUSER -ne '0' -and $env:WINRM_HIDEUSER -ne 'false' Write-Log "INFO" "Скрытие пользователя из env: $($env:WINRM_HIDEUSER) (сейчас скрыт: $isAlreadyHidden)" } else { Write-Host "" if ($isAlreadyHidden) { Write-Host " Пользователь '$wmUser' уже скрыт с экрана входа." -ForegroundColor Gray } # Write-Host " Скрыть пользователя '$wmUser' с экрана входа в Windows?" -ForegroundColor Yellow # Write-Host " (на машинах где этот пользователь единственный — отвечайте Н)" -ForegroundColor Gray # $answer = Read-Host " Скрыть? [Д/н]" $answer = 'Д' $doHide = $answer -eq '' -or $answer -match '^[ДдYy]' Write-Log "INFO" "Скрытие пользователя (ответ пользователя): $answer" } if ($doHide) { try { Hide-UserFromLogin -UserName $wmUser Write-Log "OK" "Пользователь '$wmUser' скрыт из экрана входа" } catch { Write-Log "WARN" "Не удалось скрыть пользователя: $($_.Exception.Message)" } } elseif ($isAlreadyHidden) { try { Show-UserAtLogin -UserName $wmUser Write-Log "OK" "Пользователь '$wmUser' возвращён на экран входа" } catch { Write-Log "WARN" "Не удалось вернуть пользователя на экран входа: $($_.Exception.Message)" } } else { Write-Log "INFO" "Скрытие пользователя пропущено" } } # [3/6] Самоподписанный сертификат для HTTPS Write-Host "" Write-Host "[3/6] Сертификат для HTTPS (порт 5986)..." -ForegroundColor Yellow $thumbprint = $null try { $thumbprint = New-WinRMCert } catch { Write-Log "WARN" "Ошибка создания сертификата: $($_.Exception.Message)" } # [4/6] Настройка WinRM Write-Host "" Write-Host "[4/6] Настройка WinRM..." -ForegroundColor Yellow try { Configure-WinRM -CertThumbprint $thumbprint } catch { Write-Log "ERROR" "Ошибка настройки WinRM: $($_.Exception.Message)" exit 1 } # [5/6] Правила брандмауэра Write-Host "" Write-Host "[5/6] Правила брандмауэра (TCP 5985 + 5986)..." -ForegroundColor Yellow try { Set-WinRMFirewall -WithHttps ($thumbprint -ne $null) } catch { Write-Log "WARN" "Ошибка брандмауэра: $($_.Exception.Message)" } # [6/6] Перезапуск и проверка Write-Host "" Write-Host "[6/6] Перезапуск WinRM и проверка..." -ForegroundColor Yellow # $PSSenderInfo существует только в удалённых сессиях (Invoke-Command) # Перезапуск WinRM убьёт собственную сессию — пропускаем if ($PSSenderInfo) { Write-Log "INFO" "Удалённая сессия — перезапуск WinRM пропущен (убьёт соединение)" } else { try { Restart-Service WinRM -Force Start-Sleep -Seconds 2 Write-Log "OK" "Служба WinRM перезапущена" } catch { Write-Log "WARN" "WinRM restart: $($_.Exception.Message)" } try { $testResult = Test-WSMan -ComputerName localhost -ErrorAction Stop Write-Log "OK" "WinRM отвечает: $($testResult.ProductVendor)" } catch { Write-Log "WARN" "Test-WSMan: $($_.Exception.Message)" } } Save-CredentialsInfo -UserName $wmUser -CertThumbprint $thumbprint # Итог Write-Host "" Write-Host "==========================================" -ForegroundColor Green Write-Host " WinRM настроен успешно" -ForegroundColor Green Write-Host "==========================================" -ForegroundColor Green Write-Host "" Write-Host " Пользователь : $wmUser" -ForegroundColor White Write-Host " HTTP : порт 5985" -ForegroundColor White if ($thumbprint) { Write-Host " HTTPS : порт 5986 (рекомендуется для Win11)" -ForegroundColor White } Write-Host " Лог : $LOG_FILE" -ForegroundColor Gray Write-Host " Инфо : $CREDS_FILE" -ForegroundColor Gray Write-Host "" Write-Host " Подключение:" -ForegroundColor Yellow if ($thumbprint) { Write-Host " # Windows 11 — Basic auth через HTTPS:" -ForegroundColor DarkGray Write-Host " `$opt = New-PSSessionOption -SkipCACheck -SkipCNCheck" -ForegroundColor Cyan Write-Host " Enter-PSSession -ComputerName $env:COMPUTERNAME -Port 5986 -UseSSL -Credential (Get-Credential '$wmUser') -SessionOption `$opt" -ForegroundColor Cyan } Write-Host " # NTLM (без Credential, если совпадает имя пользователя):" -ForegroundColor DarkGray Write-Host " Enter-PSSession -ComputerName $env:COMPUTERNAME" -ForegroundColor Cyan Write-Host "" Write-Log "INFO" "========== Настройка WinRM завершена =========="