Загрузка данных
Итоговая схема, которая у тебя уже работала. На каждом ПК свой логин RDP и свой IP — в скрипт OK они **не пишутся**.
В PowerShell **не копируй** строки вида `PS C:\Users\...>`.
`shell:startup` — это **Win+R**, не PowerShell. Из PowerShell папка так:
```powershell
explorer ([Environment]::GetFolderPath('Startup'))
```
---
# Что должно получиться
После ребута **без тебя**:
1. Windows сама входит под тем пользователем, которым ты заходишь по RDP.
2. Из автозагрузки открываются копии AlpineSoft (2 или 3).
3. Скрипт ждёт окна и жмёт OK/Enter в каждое.
4. Ты потом заходишь по RDP **тем же логином**, подключаешься к уже открытой сессии, не нажимаешь «Выйти».
---
# 1. Автозагрузка копий
Под тем пользователем, которым заходишь по RDP:
1. На рабочем столе ярлыки софта (`прогрев`, `прогрев2`, `клики` — какие реально нужны).
2. Win+R → `shell:startup` → Enter.
3. Скопируй в эту папку **столько ярлыков, сколько копий**:
- 3 копии → все три;
- 2 копии → только два.
Не вырезай ярлыки с рабочего стола, только копия.
---
# 2. Скрипт, который жмёт OK
Актуальный установщик — блок **«Файл Update-EnterFix.ps1»** в конце этой инструкции.
1. На сервере открой **пустое** PowerShell.
2. Вставь весь блок установщика → Enter.
3. Должно появиться: `Updated C:\alpine\Click-AlpineOk.ps1`.
Скрипт кладётся в `C:\alpine\Click-AlpineOk.ps1`.
Лог: `C:\alpine\Click-AlpineOk.log`.
Потом ярлык автозагрузки скрипта.
**Три копии:**
```powershell
$startup = [Environment]::GetFolderPath('Startup')
$lnkPath = Join-Path $startup 'AlpineOkClick.lnk'
$w = New-Object -ComObject WScript.Shell
$s = $w.CreateShortcut($lnkPath)
$s.TargetPath = 'powershell.exe'
$s.Arguments = '-NoProfile -STA -ExecutionPolicy Bypass -WindowStyle Minimized -File C:\alpine\Click-AlpineOk.ps1 -ExpectedCount 3 -TimeoutSeconds 600'
$s.WorkingDirectory = 'C:\alpine'
$s.WindowStyle = 7
$s.Save()
explorer $startup
```
**Две копии** — то же самое, но `-ExpectedCount 2`.
В папке автозагрузки: ярлыки копий + `AlpineOkClick`.
Проверка (окна с OK уже открыты):
```powershell
powershell -STA -ExecutionPolicy Bypass -File C:\alpine\Click-AlpineOk.ps1
```
В логе должно быть `posted to 3 window(s)` или `posted to 2 window(s)`.
---
# 3. Старт после ребута без тебя (Windows 11)
PowerShell **от имени администратора**:
```powershell
$path = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\PasswordLess\Device'
New-Item -Path $path -Force | Out-Null
Set-ItemProperty $path DevicePasswordLessBuildVersion -Value 0 -Type DWord
```
Затем:
1. Win+R → `netplwiz` → Enter.
2. Сними галку «Требовать ввод имени пользователя и пароля» → Применить.
3. Пользователь — **логин этого ПК** (не обязательно `99user`), пароль этой учётки два раза.
4. Параметры → Система → Питание: при питании от сети экран — **Никогда**.
5. Перезагрузка. **4 минуты не заходи по RDP.**
6. Потом RDP **тем же** логином. Если спросит про сессию — подключайся к ней. Не «Выйти».
Проверка лога:
```powershell
Get-Content C:\alpine\Click-AlpineOk.log -Tail 15
```
Нужны свежие строки и `posted to … window(s)`.
**Не включать** `ForceAutoLogon`.
**Не входить** под другим пользователем на этот ПК.
---
# Порядок на новом компьютере
1. Войти по RDP своим логином.
2. Поставить ярлыки копий в автозагрузку.
3. Вставить `Update-EnterFix.ps1` в PowerShell.
4. Создать `AlpineOkClick.lnk` с нужным `-ExpectedCount`.
5. Проверить скрипт руками на открытых окнах.
6. Включить автовход через `netplwiz` (после команды PasswordLess).
7. Ребут, 4 минуты подождать, проверить лог.
IP сервера нужен только тебе для RDP, в эти файлы его писать не надо.
---
# Файл Update-EnterFix.ps1
Вставь в пустое PowerShell на сервере целиком:
```powershell
$dest = 'C:\alpine\Click-AlpineOk.ps1'
New-Item -ItemType Directory -Force -Path 'C:\alpine' | Out-Null
$clickScript = @'
param(
[int]$ExpectedCount = 3,
[int]$TimeoutSeconds = 600
)
$LogDir = 'C:\alpine'
if (-not (Test-Path $LogDir)) { $LogDir = $PSScriptRoot }
$LogPath = Join-Path $LogDir 'Click-AlpineOk.log'
function Write-Log {
param([string]$Message)
$line = '{0} {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Message
try { Add-Content -Path $LogPath -Value $line -Encoding UTF8 } catch {}
Write-Host $line
}
Write-Log ("Start user={0} sta={1} file={2}" -f $env:USERNAME, [Threading.Thread]::CurrentThread.GetApartmentState(), $PSCommandPath)
if ([Threading.Thread]::CurrentThread.GetApartmentState() -ne 'STA') {
$p = Start-Process powershell.exe -ArgumentList @(
'-NoProfile', '-STA', '-ExecutionPolicy', 'Bypass',
'-File', $PSCommandPath,
'-ExpectedCount', $ExpectedCount,
'-TimeoutSeconds', $TimeoutSeconds
) -Wait -PassThru -WindowStyle Hidden
exit $p.ExitCode
}
Add-Type -TypeDefinition @"
using System;
using System.Text;
using System.Collections.Generic;
using System.Runtime.InteropServices;
public class AlpineWin {
public delegate bool EnumProc(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")] public static extern bool EnumWindows(EnumProc cb, IntPtr lp);
[DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll")] public static extern bool IsWindow(IntPtr hWnd);
[DllImport("user32.dll")] public static extern int GetWindowText(IntPtr hWnd, StringBuilder s, int n);
[DllImport("user32.dll")] public static extern int GetClassName(IntPtr hWnd, StringBuilder s, int n);
[DllImport("user32.dll")] public static extern bool GetClientRect(IntPtr hWnd, out RECT r);
[DllImport("user32.dll")] public static extern bool PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")] public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
public const uint WM_KEYDOWN = 0x0100;
public const uint WM_KEYUP = 0x0101;
public const uint WM_LBUTTONDOWN = 0x0201;
public const uint WM_LBUTTONUP = 0x0202;
public const int VK_RETURN = 0x0D;
public const int MK_LBUTTON = 0x0001;
[StructLayout(LayoutKind.Sequential)]
public struct RECT { public int Left; public int Top; public int Right; public int Bottom; }
public static List<IntPtr> Find() {
var found = new List<IntPtr>();
EnumWindows((hWnd, lp) => {
if (!IsWindowVisible(hWnd)) return true;
var title = new StringBuilder(256);
var cls = new StringBuilder(256);
GetWindowText(hWnd, title, 256);
GetClassName(hWnd, cls, 256);
if (title.ToString().StartsWith("AlpineSoft", StringComparison.OrdinalIgnoreCase) &&
cls.ToString() == "Qt5QWindowIcon") {
found.Add(hWnd);
}
return true;
}, IntPtr.Zero);
return found;
}
static IntPtr Pack(int x, int y) {
return (IntPtr)((y << 16) | (x & 0xFFFF));
}
public static void PressOk(IntPtr hWnd) {
PostMessage(hWnd, WM_KEYDOWN, (IntPtr)VK_RETURN, IntPtr.Zero);
PostMessage(hWnd, WM_KEYUP, (IntPtr)VK_RETURN, IntPtr.Zero);
RECT c;
GetClientRect(hWnd, out c);
int w = c.Right - c.Left;
int h = c.Bottom - c.Top;
int x = Math.Max(10, w - 158);
int y = Math.Max(10, h - 25);
IntPtr lp = Pack(x, y);
PostMessage(hWnd, WM_LBUTTONDOWN, (IntPtr)MK_LBUTTON, lp);
PostMessage(hWnd, WM_LBUTTONUP, IntPtr.Zero, lp);
SendMessage(hWnd, WM_KEYDOWN, (IntPtr)VK_RETURN, IntPtr.Zero);
SendMessage(hWnd, WM_KEYUP, (IntPtr)VK_RETURN, IntPtr.Zero);
}
}
"@
function Wait-AlpineWindows {
param([int]$Count, [datetime]$Deadline)
Write-Log "Waiting for $Count AlpineSoft window(s)..."
while ((Get-Date) -lt $Deadline) {
$found = [AlpineWin]::Find()
Write-Log (" now {0}" -f $found.Count)
if ($found.Count -ge $Count) { return $found }
Start-Sleep -Milliseconds 1500
}
return [AlpineWin]::Find()
}
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
$windows = Wait-AlpineWindows -Count $ExpectedCount -Deadline $deadline
if ($windows.Count -eq 0) {
Write-Log "No AlpineSoft windows found."
exit 1
}
Write-Log "Snapshot: $($windows.Count) window(s). PostMessage OK/Enter to each HWND."
$sent = 0
foreach ($hwnd in $windows) {
if (-not [AlpineWin]::IsWindow($hwnd)) {
Write-Log "HWND $hwnd disappeared, skip"
continue
}
[AlpineWin]::PressOk($hwnd)
$sent++
Write-Log "OK/Enter posted to HWND $hwnd"
Start-Sleep -Milliseconds 800
}
Write-Log "Done: posted to $sent window(s)."
exit 0
'@
Set-Content -Path $dest -Value $clickScript -Encoding UTF8
Write-Host "Updated $dest"
Write-Host "Test: powershell -STA -ExecutionPolicy Bypass -File C:\alpine\Click-AlpineOk.ps1"
```
На этом компьютере те же файлы уже лежат:
- `C:\Users\Usser\OneDrive\Desktop\Update-EnterFix.ps1`
- `C:\Users\Usser\OneDrive\Desktop\Click-AlpineOk.ps1`