Загрузка данных


Итоговая схема, которая у тебя уже работала. На каждом ПК свой логин 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`