commit 7105e8b165e2c349751931c2edc5119bdba1fa43 Author: Timmy Date: Fri Apr 24 17:41:53 2026 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3520a9c --- /dev/null +++ b/.gitignore @@ -0,0 +1,77 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Virtual environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Logs and temporary files +*.log +*.tmp +*.temp +logs/*.log +dumps/*.zip + +# Credentials and sensitive data +*.key +*.pem +*.p12 +*.pfx +*.rdp +config/*.json.bak +config/*-backup.* +.netrc +credentials.* +secrets.* +*.env.local +*.env.production + +# PowerShell +*.ps1.bak +*.ps1~ + +# Project specific +dumps/ +temp/ +tmp/ \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..a43fc0d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,142 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## 專案概述 + +這是一個 Windows 遠端管理工具包,專門設計用於從 macOS/Linux 系統遠端配置和管理 Windows 機器(預設目標:PC-74269 at 192.168.88.112)。工具包提供自動化腳本進行系統 debloating、應用程式安裝、安全性配置和診斷。 + +## 核心架構模式 + +### 雙腳本架構 +每個功能都包含兩個檔案: +- `.ps1` - 在目標 Windows 機器上執行的 PowerShell 腳本 +- `.sh` - macOS/Linux shell wrapper,透過 SSH 上傳並執行 PowerShell 腳本 + +### 執行流程 +1. Shell 腳本使用 `sshpass` + `scp` 將 PowerShell 腳本上傳到目標機器 +2. 透過 SSH 遠端執行:`powershell -NoProfile -ExecutionPolicy Bypass` +3. 輸出同時顯示在終端並記錄到 `logs/` 目錄(帶時間戳) + +### 雙連線方法支援 +**SSH 方法(穩定,適合大型腳本):** +- 穩定可靠,處理大型腳本(>3KB)無問題 +- 所有現有 `.sh` 腳本使用此方法 +- 執行時間:~22 秒(recon 腳本) + +**WinRM 方法(快速,適合快速任務):** +- 1.9 倍執行速度(12 秒 vs 22 秒) +- 原生 Windows 遠端管理,更好的 PowerShell 整合 +- 受命令列長度限制,適合小型腳本 + +## 常用命令 + +### 環境需求設置 +```bash +# 安裝 sshpass(SSH 方法必需) +brew install hudochenkov/sshpass/sshpass + +# 安裝 Python WinRM 庫(WinRM 方法) +pip3 install pywinrm +``` + +### 系統偵察和驗證 +```bash +./recon.sh # 完整系統清單(15 個區塊) +./verify-ai-removed.sh # 驗證 Windows AI 組件已移除 +./verify-debloat.sh # 驗證 Win11Debloat 變更已套用 +``` + +### 核心系統配置 +```bash +./firewall-allow-ssh.sh # 啟用永久 SSH 存取 +./activate-windows.sh # 透過 KMS 啟用 Windows +./remove-windows-ai.sh # 移除 Windows AI/Copilot 組件 +./win11debloat.sh # 移除膨脹軟體並停用遙測 +``` + +### 連線方法選擇 +```bash +# SSH 方法(預設,適合所有腳本) +./recon.sh + +# WinRM 方法(更快的替代方案) +python3 test_simple_winrm.py +python3 benchmark_ssh_vs_winrm.py + +# 智慧方法選擇 +python3 smart_executor.py script.ps1 +``` + +## 重要配置檔案 + +### `win11debloat-config.json` +Win11Debloat 操作配置。使用包含 `Tweaks`、`Deployment` 和 `Apps` 區塊的架構。所有調整名稱必須存在於 Win11Debloat 的 `Config/Features.json` 中,否則會被拒絕為「no importable data」。 + +### `thunderbird-config.json` +Thunderbird 設置的電子郵件帳戶配置。支援各種提供商(Gmail、Outlook、iCloud、自定義伺服器)的 IMAP/SMTP。 + +### `nirsoft-config.json` +定義要安裝的 16 個 NirSoft 診斷工具,包括下載 URL 和安裝路徑。 + +## 關鍵架構陷阱 + +### PowerShell 編碼問題 +中文 Windows 系統將無 BOM 的 UTF-8 腳本讀取為 CP950。腳本會自動為下載的 PowerShell 檔案添加 UTF-8 BOM 以避免解析失敗。 + +### 防火牆持久性 +Windows 防火牆設定檔在重啟後會重置。防火牆腳本建立永久規則並啟用所有設定檔,確保 SSH 存取持續有效。 + +### Thunderbird 設定檔處理 +Thunderbird 72+ 使用 `profiles.ini` 中的 `[Install]` 區塊而非 `Default=1`。設置腳本正確處理這種變更。 + +### Win11Debloat 範圍限制 +靜默模式僅將 UI 設定套用到執行使用者。使用 `apply-ui-to-user.sh` 將設定同步到其他使用者。 + +### WinRM 目標需求 +- 目標上運行的 WinRM 服務(連接埠 5985 HTTP 或 5986 HTTPS) +- 執行過 `Enable-PSRemoting -Force` +- 為 Windows 遠端管理啟用防火牆規則 +- 對於 HTTP:WinRM 配置中的 `AllowUnencrypted=true` + +## 環境變數覆寫 + +所有腳本支援連線參數覆寫: +```bash +HOST=192.168.x.x USER_NAME=foo PASS='xxx' ./script.sh +MODE=Apply|Revert ./remove-windows-ai.sh # AI 移除模式 +TARGET_USER=someone ./apply-ui-to-user.sh # UI 設定目標使用者 +``` + +## 建議執行順序(新機器) + +1. `firewall-allow-ssh.sh` - 確保永久 SSH 存取 +2. `enable-winrm.sh` - 配置 WinRM(如使用 WinRM 方法) +3. `recon.sh` - 系統清單 +4. `activate-windows.sh` - Windows 啟用 +5. AI 移除 + 重啟 + 驗證 +6. Win11Debloat + 重啟 + 驗證 +7. 應用程式安裝 +8. 帳戶管理(重新命名/隱藏應最後進行) + +## 日誌記錄和輸出 + +- 所有操作在 `logs/` 目錄建立帶時間戳的日誌 +- 格式:`{operation}-{host}-{timestamp}.log` +- 診斷傾印儲存到 `dumps/` 目錄為 ZIP 檔案 +- 腳本提供即時輸出並同時記錄 + +## 遠端目標需求 + +- 啟用 OpenSSH Server 的 Windows 機器(連接埠 22) +- 預設憑證:`Admin@192.168.88.112` / `P@ssw0rd!` +- 需要 PowerShell 5.1(非 PowerShell 7/Core) +- 目標必須可從執行環境存取 + +## 故障排除要點 + +1. **重啟後 SSH 無法連線**:Private/Public 設定檔會恢復為系統預設的阻擋入站設定 +2. **Thunderbird 設置後看不到帳戶**:確認 `[Install]` 區塊配置正確 +3. **Telegram Desktop 安裝限制**:不支援機器範圍安裝,使用 per-user 變通方法 +4. **Win11Debloat 配置架構**:使用 `Tweaks`/`Deployment`/`Apps`,不是 `Settings` +5. **NirSoft 工具 URL 變更**:部分下載連結可能失效,腳本會跳過並繼續 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..13898ff --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# Windows Remote Toolkit + +A comprehensive remote management toolkit for configuring and managing Windows machines from macOS/Linux systems. + +## Overview + +This toolkit provides automated scripts for system debloating, application installation, security configuration, and diagnostics on remote Windows machines via SSH and WinRM. + +## Quick Start + +```bash +# Enable SSH access +./firewall-allow-ssh.sh + +# System reconnaissance +./recon.sh + +# Remove Windows AI components +./remove-windows-ai.sh +``` + +See [CLAUDE.md](CLAUDE.md) for detailed documentation and usage instructions. \ No newline at end of file diff --git a/config/nirsoft-config.json b/config/nirsoft-config.json new file mode 100644 index 0000000..e90d8de --- /dev/null +++ b/config/nirsoft-config.json @@ -0,0 +1,22 @@ +{ + "Tools": [ + { "Name": "BlueScreenView", "Url": "https://www.nirsoft.net/utils/bluescreenview-x64.zip", "Exe": "BlueScreenView.exe" }, + { "Name": "LastActivityView", "Url": "https://www.nirsoft.net/utils/lastactivityview.zip", "Exe": "LastActivityView.exe" }, + { "Name": "WhatInStartup", "Url": "https://www.nirsoft.net/utils/whatinstartup-x64.zip", "Exe": "WhatInStartup.exe" }, + { "Name": "OpenedFilesView", "Url": "https://www.nirsoft.net/utils/ofview-x64.zip", "Exe": "OpenedFilesView.exe" }, + { "Name": "InstalledAppView", "Url": "https://www.nirsoft.net/utils/installedappview-x64.zip", "Exe": "InstalledAppView.exe" }, + { "Name": "DevManView", "Url": "https://www.nirsoft.net/utils/devmanview-x64.zip", "Exe": "DevManView.exe" }, + { "Name": "MyEventViewer", "Url": "https://www.nirsoft.net/utils/myeventviewer-x64.zip", "Exe": "MyEventViewer.exe" }, + { "Name": "RegScanner", "Url": "https://www.nirsoft.net/utils/regscanner-x64.zip", "Exe": "RegScanner.exe" }, + { "Name": "WifiInfoView", "Url": "https://www.nirsoft.net/utils/wifiinfoview.zip", "Exe": "WifiInfoView.exe" }, + { "Name": "WirelessNetView", "Url": "https://www.nirsoft.net/utils/wirelessnetview.zip", "Exe": "WirelessNetView.exe" }, + { "Name": "DNSDataView", "Url": "https://www.nirsoft.net/utils/dnsdataview.zip", "Exe": "DNSDataView.exe" }, + { "Name": "IPNetInfo", "Url": "https://www.nirsoft.net/utils/ipnetinfo.zip", "Exe": "ipnetinfo.exe" }, + { "Name": "ProcessActivityView", "Url": "https://www.nirsoft.net/utils/processactivityview.zip", "Exe": "ProcessActivityView.exe" }, + { "Name": "DiskCountersView", "Url": "https://www.nirsoft.net/utils/diskcountersview.zip", "Exe": "DiskCountersView.exe" }, + { "Name": "USBDeview", "Url": "https://www.nirsoft.net/utils/usbdeview-x64.zip", "Exe": "USBDeview.exe" }, + { "Name": "TurnedOnTimesView", "Url": "https://www.nirsoft.net/utils/turnedontimesview.zip", "Exe": "TurnedOnTimesView.exe" } + ], + "InstallRoot": "C:\\Program Files\\NirSoft", + "ShortcutRoot": "C:\\Users\\Admin\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\NirSoft" +} diff --git a/config/remove-apps-config.json b/config/remove-apps-config.json new file mode 100644 index 0000000..79c35c3 --- /dev/null +++ b/config/remove-apps-config.json @@ -0,0 +1,28 @@ +{ + "RemoveAppx": [ + "Microsoft.BingNews", + "Microsoft.BingWeather", + "Microsoft.GetHelp", + "Microsoft.MicrosoftSolitaireCollection", + "Microsoft.Todos", + "Microsoft.Windows.DevHome", + "Microsoft.WindowsAlarms", + "Microsoft.WindowsCamera", + "Microsoft.WindowsFeedbackHub", + "Microsoft.WindowsSoundRecorder", + "Microsoft.Xbox.TCUI", + "Microsoft.XboxGameCallableUI", + "Microsoft.XboxIdentityProvider", + "Microsoft.XboxSpeechToTextOverlay", + "Microsoft.YourPhone", + "Microsoft.ZuneMusic", + "MicrosoftCorporationII.QuickAssist", + "MicrosoftWindows.Client.WebExperience", + "MicrosoftWindows.CrossDevice", + "MSTeams", + "Microsoft.PowerAutomateDesktop", + "Microsoft.Windows.Photos" + ], + "RemoveOffice": true, + "_comment_kept": "Intentionally kept: WindowsStore, WindowsCalculator, WindowsNotepad, WindowsTerminal, ScreenSketch (Snipping Tool), Paint, MicrosoftStickyNotes, SecHealthUI (Defender), DesktopAppInstaller (winget), and all OEM/driver apps (NVIDIA, Realtek, Intel, Dolby)." +} diff --git a/config/thunderbird-config.json b/config/thunderbird-config.json new file mode 100644 index 0000000..5ac02ec --- /dev/null +++ b/config/thunderbird-config.json @@ -0,0 +1,23 @@ +{ + "WindowsUser": "user", + "ProfileName": "default", + "Identity": { + "FullName": "Timmy", + "Email": "timmy.lo@automodules.tw" + }, + "Incoming": { + "Type": "imap", + "Hostname": "mail.automodules.tw", + "Port": 993, + "SocketType": "SSL", + "Username": "timmy.lo", + "AuthMethod": "normal" + }, + "Outgoing": { + "Hostname": "mail.automodules.tw", + "Port": 465, + "SocketType": "SSL", + "Username": "timmy.lo", + "AuthMethod": "normal" + } +} diff --git a/config/win11debloat-config.json b/config/win11debloat-config.json new file mode 100644 index 0000000..a7ab8ad --- /dev/null +++ b/config/win11debloat-config.json @@ -0,0 +1,60 @@ +{ + "Version": "1.0", + "Tweaks": [ + { "Name": "RemoveApps", "Value": true }, + { "Name": "RemoveGamingApps", "Value": true }, + { "Name": "RemoveCommApps", "Value": true }, + { "Name": "RemoveW11Outlook", "Value": true }, + + { "Name": "DisableTelemetry", "Value": true }, + { "Name": "DisableSearchHistory", "Value": true }, + { "Name": "DisableBing", "Value": true }, + { "Name": "DisableStoreSearchSuggestions", "Value": true }, + { "Name": "DisableSearchHighlights", "Value": true }, + { "Name": "DisableLockscreenTips", "Value": true }, + { "Name": "DisableSuggestions", "Value": true }, + { "Name": "DisableDesktopSpotlight", "Value": true }, + { "Name": "DisableFindMyDevice", "Value": true }, + + { "Name": "DisableEdgeAds", "Value": true }, + { "Name": "DisableBraveBloat", "Value": true }, + { "Name": "DisableSettings365Ads", "Value": true }, + { "Name": "DisableSettingsHome", "Value": true }, + + { "Name": "DisableCopilot", "Value": true }, + { "Name": "DisableRecall", "Value": true }, + { "Name": "DisableClickToDo", "Value": true }, + { "Name": "DisableAISvcAutoStart", "Value": true }, + { "Name": "DisablePaintAI", "Value": true }, + { "Name": "DisableNotepadAI", "Value": true }, + { "Name": "DisableEdgeAI", "Value": true }, + + { "Name": "PreventUpdateAutoReboot", "Value": true }, + { "Name": "DisableModernStandbyNetworking", "Value": true }, + { "Name": "DisableDVR", "Value": true }, + { "Name": "DisableGameBarIntegration", "Value": true }, + + { "Name": "ShowHiddenFolders", "Value": true }, + { "Name": "ShowKnownFileExt", "Value": true }, + { "Name": "EnableDarkMode", "Value": true }, + + { "Name": "TaskbarAlignLeft", "Value": true }, + { "Name": "HideSearchTb", "Value": true }, + { "Name": "HideTaskview", "Value": true }, + { "Name": "HideChat", "Value": true }, + { "Name": "DisableWidgets", "Value": true }, + + { "Name": "DisableStartRecommended", "Value": true }, + + { "Name": "Hide3dObjects", "Value": true }, + { "Name": "ExplorerToThisPC", "Value": true }, + { "Name": "HideGallery", "Value": true }, + + { "Name": "DisableDragTray", "Value": true } + ], + "Deployment": [ + { "Name": "CreateRestorePoint", "Value": true }, + { "Name": "RestartExplorer", "Value": false }, + { "Name": "AppRemovalScopeIndex", "Value": 0 } + ] +} diff --git a/docs/CLAUDE.md b/docs/CLAUDE.md new file mode 100644 index 0000000..4d12755 --- /dev/null +++ b/docs/CLAUDE.md @@ -0,0 +1,167 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a Windows remote administration toolkit designed for configuring and managing a specific Windows machine (PC-74269 at 192.168.88.112) from macOS/Linux systems. The toolkit provides automated scripts for system debloating, application installation, security configuration, and diagnostics. + +## Architecture Pattern + +**Dual-Script Architecture**: Each functionality consists of two files: +- `.ps1` - PowerShell script that runs on the target Windows machine +- `.sh` - macOS/Linux shell wrapper that uploads and executes the PowerShell script via SSH + +**Execution Flow**: +1. Shell script uses `sshpass` + `scp` to upload PowerShell script to target +2. Executes PowerShell script remotely via SSH with `powershell -NoProfile -ExecutionPolicy Bypass` +3. Output is simultaneously displayed and logged to `logs/` with timestamps + +## Key Commands + +### Prerequisites +```bash +# Install sshpass (required for all operations) +brew install hudochenkov/sshpass/sshpass +``` + +### System Reconnaissance +```bash +./recon.sh # Complete system inventory (15 sections) +./verify-ai-removed.sh # Verify Windows AI components removed +./verify-debloat.sh # Verify Win11Debloat changes applied +``` + +### Core System Configuration +```bash +./firewall-allow-ssh.sh # Enable permanent SSH access +./activate-windows.sh # Activate Windows via KMS +./remove-windows-ai.sh # Remove Windows AI/Copilot components +./win11debloat.sh # Remove bloatware and disable telemetry +``` + +### Application Management +```bash +./install-thunderbird.sh # Install Thunderbird email client +./setup-thunderbird.sh # Configure IMAP/SMTP (edit thunderbird-config.json first) +./install-telegram.sh # Install Telegram for target user +``` + +### Diagnostics and Tools +```bash +./install-nirsoft.sh # Install 16 NirSoft diagnostic tools +./nirsoft-dump.sh # Collect and package system diagnostic data +``` + +### Environment Variables +All scripts support connection overrides: +```bash +HOST=192.168.x.x USER_NAME=foo PASS='xxx' ./script.sh +MODE=Apply|Revert ./remove-windows-ai.sh # AI removal modes +TARGET_USER=someone ./apply-ui-to-user.sh # UI settings target +``` + +## Configuration Files + +### `win11debloat-config.json` +Configuration for Win11Debloat operations. Uses schema with `Tweaks`, `Deployment`, and `Apps` sections. All tweak names must exist in Win11Debloat's `Config/Features.json` or they'll be rejected as "no importable data". + +### `thunderbird-config.json` +Email account configuration for Thunderbird setup. Supports IMAP/SMTP with various providers (Gmail, Outlook, iCloud, custom servers). + +### `nirsoft-config.json` +Defines the 16 NirSoft diagnostic tools to install, their download URLs, and installation paths. + +## Important Gotchas + +1. **PowerShell Encoding**: Chinese Windows systems read UTF-8 scripts as CP950 without BOM. Scripts automatically add UTF-8 BOM to downloaded PowerShell files. + +2. **Firewall Persistence**: Windows firewall profiles reset on reboot. The firewall script creates permanent rules and enables all profiles. + +3. **Thunderbird Profiles**: Thunderbird 72+ uses `[Install]` sections in profiles.ini, not `Default=1`. Setup script handles this correctly. + +4. **Win11Debloat Scope**: Silent mode only applies UI settings to the executing user. Use `apply-ui-to-user.sh` to sync settings to other users. + +5. **Registry Paths**: Many "common knowledge" registry paths for Windows settings are incorrect. Always verify actual paths used by tools. + +## Logging and Output + +- All operations create timestamped logs in `logs/` directory +- Format: `{operation}-{host}-{timestamp}.log` +- Diagnostic dumps saved to `dumps/` directory as ZIP files +- Scripts provide real-time output while simultaneously logging + +## Remote Target Requirements + +- Windows machine with OpenSSH Server enabled on port 22 +- Default credentials: `Admin@192.168.88.112` / `P@ssw0rd!` +- Target must be accessible from execution environment +- PowerShell 5.1 required (not PowerShell 7/Core) + +## Connection Methods + +This toolkit supports two connection methods with different strengths: + +### SSH Method (Stable, for Large Scripts) +- ✅ Proven stability and reliability +- ✅ Handles large scripts (>3KB) without issues +- ✅ File upload capabilities via `scp` +- ✅ All existing `.sh` scripts use this method +- ⏱️ Execution time: ~22 seconds for recon +- Requires OpenSSH Server on target Windows machine + +### WinRM Method (Fast, for Quick Tasks) +- ⚡ **1.9x faster execution** (12s vs 22s for recon) +- ✅ Native Windows remote management +- ✅ Better PowerShell integration and error handling +- ✅ Structured logging and real-time output +- ⚠️ Limited by command line length for large scripts +- Requires WinRM service configuration on target + +```bash +# SSH method (reliable for large scripts) +./recon.sh + +# WinRM method (faster for small tasks) +python3 test_simple_winrm.py +python3 benchmark_ssh_vs_winrm.py + +# Smart method selection +python3 smart_executor.py script.ps1 +``` + +## WinRM Setup and Usage + +### Prerequisites for WinRM +```bash +# Install Python WinRM library +pip3 install pywinrm + +# Generate WinRM wrapper scripts +python3 generate_winrm_wrappers.py +``` + +### WinRM Configuration Scripts +- `check-winrm.sh` - Verify WinRM configuration status +- `enable-winrm.sh` - Configure WinRM service for remote access +- `fix-winrm.sh` - Fix common WinRM connectivity issues +- `test_winrm_python.py` - Test WinRM connectivity with Python +- `winrm_executor.py` - Core WinRM execution framework + +### WinRM Target Requirements +- WinRM service running on port 5985 (HTTP) or 5986 (HTTPS) +- `Enable-PSRemoting -Force` executed on target +- Firewall rules enabled for Windows Remote Management +- For HTTP: `AllowUnencrypted=true` in WinRM configuration + +## Recommended Execution Order + +For new machines, follow this sequence: +1. `firewall-allow-ssh.sh` - Ensure permanent SSH access +2. `enable-winrm.sh` - Configure WinRM (if using WinRM method) +3. `recon.sh` or `recon-winrm.py` - System inventory +4. `activate-windows.sh` - Windows activation +5. AI removal + reboot + verification +6. Win11Debloat + reboot + verification +7. Application installations +8. Account management (rename/hide should be done last) \ No newline at end of file diff --git a/docs/FILE_ORGANIZATION.md b/docs/FILE_ORGANIZATION.md new file mode 100644 index 0000000..594ba3d --- /dev/null +++ b/docs/FILE_ORGANIZATION.md @@ -0,0 +1,114 @@ +# 檔案組織結構 + +## 📁 目錄說明 + +### `/scripts/` - 核心執行腳本 +``` +scripts/ +├── ssh/ # SSH 方案腳本(.sh 文件) +├── winrm/ # WinRM 方案腳本(.py 文件) +└── powershell/ # PowerShell 腳本(.ps1 文件) +``` + +### `/config/` - 配置文件 +``` +config/ +├── nirsoft-config.json # NirSoft 工具配置 +├── thunderbird-config.json # Thunderbird 郵件配置 +├── win11debloat-config.json # Win11Debloat 配置 +└── remove-apps-config.json # 應用程式移除配置 +``` + +### `/docs/` - 文檔資料 +``` +docs/ +├── README.md # 主要說明文檔 +├── CLAUDE.md # Claude Code 指導文檔 +├── SSH_vs_WinRM_Comparison.md # 方案比較分析 +└── WINRM_EXPLORATION_SUMMARY.md # WinRM 探索總結 +``` + +### `/tools/` - 工具和測試 +``` +tools/ +└── (診斷和測試工具) +``` + +### `/examples/` - 示例和雜項 +``` +examples/ +├── *.rdp # 遠端桌面連接文件 +├── *.cmd # 批次處理文件 +├── *.reg # 登錄檔案 +└── *BACKUP* # 備份文件 +``` + +### `/logs/` - 執行日誌 +``` +logs/ +└── (自動生成的執行日誌) +``` + +### `/dumps/` - 診斷資料 +``` +dumps/ +└── (NirSoft 診斷資料備份) +``` + +## 🎯 快速導航 + +### 常用腳本執行: +```bash +# SSH 方案(從 scripts/ssh/ 目錄) +cd scripts/ssh && ./recon.sh +cd scripts/ssh && ./install-thunderbird.sh + +# WinRM 方案(從 scripts/winrm/ 目錄) +cd scripts/winrm && python3 test_simple_winrm.py +cd scripts/winrm && python3 smart_executor.py ../powershell/recon.ps1 + +# 直接執行 PowerShell 腳本(透過 SSH 或 WinRM) +# scripts/powershell/ 目錄包含所有 .ps1 文件 +``` + +### 配置文件編輯: +```bash +# 編輯配置(從 config/ 目錄) +cd config && nano thunderbird-config.json +cd config && nano win11debloat-config.json +``` + +### 查看文檔: +```bash +# 查看文檔(從 docs/ 目錄) +cd docs && cat README.md +cd docs && cat CLAUDE.md +``` + +## 📋 文件清單概覽 + +### SSH 腳本 (scripts/ssh/) +- 所有原有的 `.sh` 包裝腳本 +- 包含 SSH 連接、腳本上傳、執行邏輯 + +### PowerShell 腳本 (scripts/powershell/) +- 所有實際在 Windows 上執行的 `.ps1` 腳本 +- 包含系統配置、應用安裝、診斷等功能 + +### WinRM 腳本 (scripts/winrm/) +- Python WinRM 執行器和測試工具 +- 智能執行選擇器 +- WinRM 連接診斷工具 + +### 配置文件 (config/) +- 各種工具和服務的 JSON 配置文件 +- 可根據需求編輯自定義設定 + +## 🔄 使用建議 + +1. **保持目錄結構** - 腳本中的相對路徑已更新 +2. **從適當目錄執行** - SSH 腳本從 ssh/ 目錄執行,WinRM 從 winrm/ 執行 +3. **配置文件共享** - config/ 目錄被所有腳本共享使用 +4. **查看文檔** - 所有說明和比較文件在 docs/ 目錄 + +這種組織方式讓專案更清晰、易於維護和理解! \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..be4cbfc --- /dev/null +++ b/docs/README.md @@ -0,0 +1,371 @@ +# 192.168.88.108 Toolkit — PC-74269 + +遠端設定 `PC-74269`(192.168.88.108,Lenovo ThinkPad T480,Windows 11 build 26200)的腳本集合。所有腳本皆為「Mac 端 shell wrapper 一鍵執行 → scp PowerShell 腳本到目標 → 遠端執行 → 同時輸出到終端 + 存帶時間戳的 log」。 + +## 目錄結構 + +``` +. +├── README.md # 本文件 +├── logs/ # 每次執行的完整輸出(帶時間戳) +│ +├── 盤點 / 驗證 +│ ├── recon.ps1 / .sh 機器盤點(15 段) +│ ├── verify-ai-removed.ps1 / .sh 驗證 AI 元件已移除 +│ └── verify-debloat.ps1 / .sh 驗證 Win11Debloat 變更 +│ +├── AI 移除 +│ ├── remove-windows-ai.ps1 / .sh zoicware/RemoveWindowsAI +│ +├── 系統 debloat +│ ├── win11debloat.ps1 / .sh Raphire/Win11Debloat +│ ├── win11debloat-config.json Win11Debloat 設定檔(需編輯) +│ └── apply-ui-to-user.ps1 / .sh 將 UI 設定套用到指定 user(補 Win11Debloat 只套 Admin 的缺) +│ +├── 應用程式 +│ ├── install-thunderbird.ps1 / .sh 安裝 Thunderbird +│ ├── setup-thunderbird.ps1 / .sh 設定 IMAP/SMTP 帳號 +│ ├── thunderbird-config.json Thunderbird 帳號設定檔(需編輯) +│ └── install-telegram.ps1 / .sh 替指定 Windows 使用者安裝 Telegram +│ +├── 系統設定 +│ ├── firewall-allow-ssh.ps1 / .sh 永久允許 TCP 22 入站 + 啟用防火牆 profile +│ ├── rename-hide-accounts.ps1 / .sh 本機帳號更名 + 從登入畫面隱藏 +│ ├── configure-ime.ps1 / .sh 設定輸入法(英文優先,Ctrl+Space 切換) +│ └── activate-windows.ps1 / .sh Windows 手動啟用腳本 +│ +└── 診斷工具 + ├── install-nirsoft.ps1 / .sh 安裝 16 個 NirSoft 診斷工具 + ├── nirsoft-config.json NirSoft 工具配置檔 + ├── nirsoft-dump.ps1 / .sh 一鍵收集系統診斷資料並打包 + └── dumps/ 診斷資料備份目錄 +``` + +## 前置需求 + +- macOS / Linux 本機,已裝 `sshpass`(`brew install hudochenkov/sshpass/sshpass`) +- 目標機器已啟用 OpenSSH Server,22 port 可達 +- 預設連線:`Admin@192.168.88.108` / `P@ssw0rd!` +- 可用環境變數覆寫:`HOST=...` `USER_NAME=...` `PASS=...` + +## 建議執行順序(全新機器) + +1. **`firewall-allow-ssh.sh`** — 先確保 SSH 永遠通,避免重開機後又手動關防火牆 +2. **`recon.sh`** — 盤點,確認狀態 +3. **`activate-windows.sh`** — Windows 啟用(若尚未啟用) +4. **`remove-windows-ai.sh`** → 重開機 → **`verify-ai-removed.sh`** — 拔 AI +5. **`win11debloat.sh`** → 重開機 → **`verify-debloat.sh`** — 系統 debloat +6. **`apply-ui-to-user.sh`**(若有其他使用者)— 把 Win11Debloat 的 UI 設定套用到沒被套到的帳號 +7. **`install-nirsoft.sh`** — 安裝診斷工具(建議早期安裝,方便後續故障排除) +8. **`install-thunderbird.sh`** → 編輯 `thunderbird-config.json` → **`setup-thunderbird.sh`** — 設信箱 +9. **`install-telegram.sh`** — 裝 Telegram 給目標使用者 +10. **`rename-hide-accounts.sh`** — 帳號更名 / 隱藏 Admin(最後再做,避免中途登入名改變) + +## 故障排除 / 診斷 + +- **`nirsoft-dump.sh`** — 一鍵收集系統狀態並打包成 ZIP(網路、進程、服務、USB、事件日誌等) +- **`recon.sh`** — 詳細系統盤點(15 段) + +## 各腳本詳細 + +### `recon.ps1` / `recon.sh` +機器盤點。不修改任何設定,只讀資訊。15 段:電腦、開機時間、本機帳號、Administrators 群組、磁碟、IPv4 位址、監聽中的 TCP port、防火牆 profile、SMB 分享、Defender 狀態、非 MS 安裝軟體、啟動項、非 MS 排程、SSH 服務、48 小時內登入紀錄。 + +```bash +./recon.sh +``` + +### `firewall-allow-ssh.ps1` / `.sh` +建立永久 TCP 22 入站允許規則(`-Profile Any`,Domain/Private/Public 全部生效),並**把三個 profile 都啟用回來**。跑過一次以後,重開機 SSH 自動通。 + +```bash +./firewall-allow-ssh.sh +``` + +### `remove-windows-ai.ps1` / `.sh` +執行 [zoicware/RemoveWindowsAI](https://github.com/zoicware/RemoveWindowsAI)。 + +**動作:** +1. 驗證是 Windows PowerShell 5.1(**不是 pwsh 7**)+ Administrator token +2. 下載 `RemoveWindowsAi.ps1` 到 `%TEMP%\RemoveWindowsAI\` +3. **自動補 UTF-8 BOM**(PS 5.1 在 zh-TW 系統預設以 CP950 讀無 BOM 檔,會 parse 失敗) +4. 以 `-nonInteractive -AllOptions -backupMode -EnableLogging` 執行 + +```bash +./remove-windows-ai.sh # Apply(預設) +MODE=Revert ./remove-windows-ai.sh # 還原(需 backupMode 先跑過) +``` + +執行完請重開機。 + +### `verify-ai-removed.ps1` / `.sh` +只讀,不修改。檢查服務、Appx、Recall、群組原則登錄鍵、Notepad Rewrite、IntegratedServicesRegionPolicySet.json、RemoveAI 排程、Copilot/AIX SystemApps、系統還原點等。以後 Windows Update 偷塞 AI 回來時跑一次就看得到。 + +```bash +./verify-ai-removed.sh +``` + +### `win11debloat.ps1` / `.sh` + `win11debloat-config.json` +執行 [Raphire/Win11Debloat](https://github.com/Raphire/Win11Debloat)(移除預裝、關閉 telemetry/廣告、UI 調整)。 + +**動作:** +1. 驗證 PS 5.1 + Administrator +2. 下載整個 repo zip(Win11Debloat 需要 `Config/`、`Regfiles/`、`Schemas/` 等相對路徑) +3. **對所有 `.ps1` 檔補 UTF-8 BOM** +4. 以 `-Silent -Config my-config.json -LogPath ...` 執行 + +config 結構(Win11Debloat 的 schema): +```json +{ + "Version": "1.0", + "Tweaks": [ {"Name":"DisableTelemetry","Value":true}, ... ], + "Deployment": [ + {"Name":"CreateRestorePoint","Value":true}, + {"Name":"RestartExplorer","Value":false}, + {"Name":"AppRemovalScopeIndex","Value":0} + ] +} +``` + +`Tweaks` 項目名稱必須在 Win11Debloat 的 `Config/Features.json` 裡,否則會被當成「no importable data」拒絕。 + +**預設 config 已包含:** 移除 Xbox/Mail/People/Outlook/Copilot/BingSearch/StartExperiences、Telemetry、搜尋 suggestions、Bing/Spotlight、Edge 廣告、Brave bloat、Settings 廣告、AI(重複套用,無害)、DVR/GameBar、暗色模式、工作列靠左、隱藏 Widgets/Chat/Task View/Search、Explorer 首頁 This PC、隱藏 Gallery。 + +**刻意不包含(不要裝東西):** `EnableWindowsSandbox`、`EnableWindowsSubsystemForLinux`。 + +```bash +./win11debloat.sh +``` + +### `verify-debloat.ps1` / `.sh` +只讀驗證。檢查 Appx 移除、Telemetry、Widgets (Dsh 政策)、GameDVR、Edge 廣告政策、Bing 搜尋(per-user)、Spotlight(per-user)、UI 設定(per-user 掃 HKEY_USERS 下每個 hive)。 + +```bash +./verify-debloat.sh +``` + +### `apply-ui-to-user.ps1` / `.sh` +**補 Win11Debloat 的缺**:`-Silent` 模式只把 UI 設定套到執行者(Admin)的 HKCU,其他 user 沒套到。這個腳本把 UI 設定直接寫到指定使用者的 hive。 + +- 自動偵測 user hive 是否已載入(登入中 → 已載入 → 直接寫) +- 若未載入 → `reg load` 載 `C:\Users\\NTUSER.DAT`,寫完 `reg unload` + +```bash +./apply-ui-to-user.sh # 預設 TARGET_USER=user +TARGET_USER=someone ./apply-ui-to-user.sh # 換目標 +``` + +套用的設定:顯示副檔名/隱藏檔、工作列靠左、隱藏 Task View、Explorer 首頁 This PC、暗色模式、停用 Bing 搜尋 suggestions、停用 Spotlight。 + +### `install-thunderbird.ps1` / `.sh` +安裝 Thunderbird:winget 先試,失敗 fallback 到 Mozilla CDN 下載 zh-TW installer 用 `/S` 靜默安裝。 + +```bash +./install-thunderbird.sh +``` + +### `setup-thunderbird.ps1` / `.sh` + `thunderbird-config.json` +設定 IMAP/SMTP 帳號(**只設 server/identity,不塞密碼** — 首次啟動時跳視窗輸入)。 + +先編輯 `thunderbird-config.json`,然後: +```bash +./setup-thunderbird.sh +``` + +**常見服務商:** +| 服務 | IMAP | SMTP | +|------|------|------| +| Gmail | `imap.gmail.com:993` SSL | `smtp.gmail.com:465` SSL | +| Outlook / M365 | `outlook.office365.com:993` SSL | `smtp.office365.com:587` STARTTLS | +| iCloud | `imap.mail.me.com:993` SSL | `smtp.mail.me.com:587` STARTTLS | + +**注意:** Thunderbird 72+ 用 `profiles.ini` 中 `[Install]` 區塊決定 profile(不是 `Default=1`)。若 Thunderbird 已經被打開過自動建立了 `default-release` profile,本腳本會改寫 Install 區塊指向我們的 `default`。 + +### `install-telegram.ps1` / `.sh` +替指定 Windows 使用者安裝 Telegram Desktop。 + +**⚠️ Telegram Desktop 的 Inno Setup 不支援 machine-wide 安裝**(`PrivilegesRequired=lowest` 且不允許 override),必為 per-user。本腳本用以下變通做法: + +1. 若發現 Admin 底下有殘留的安裝 → 先靜默卸載 +2. 以 Admin 身份執行 installer 但用 `/DIR=` 指向目標使用者的 `%APPDATA%`,並加 `/NOICONS` +3. `icacls` 把整個安裝樹的控制權交給目標使用者 +4. 手動在目標使用者的 Start Menu / Desktop 建捷徑 + +預設目標是 `user`,改目標請編輯 `.ps1` 頂部的 `param($TargetUser = 'user')`。 + +```bash +./install-telegram.sh +``` + +### `rename-hide-accounts.ps1` / `.sh` +兩件事合一: +1. 把本機帳號 `user` 重新命名為 `User`(大小寫變更,SID 不變) +2. 把 `Admin` 加入 `SpecialAccounts\UserList = 0`,從登入畫面隱藏(仍可手動打 `Admin` 登入) + +```bash +./rename-hide-accounts.sh +``` + +### `activate-windows.ps1` / `.sh` +手動啟用 Windows 使用 KMS 方法。自動偵測 Windows 版本並使用對應的 GVLK(Generic Volume License Key),然後連接 KMS 伺服器進行啟用。 + +**動作:** +1. 安裝對應版本的 GVLK(如 Windows 10 Pro: `W269N-WFGWX-YVC9B-4J6C9-T83GX`) +2. 設定 KMS 伺服器(`kms.digiboy.ir`) +3. 執行啟用並驗證結果 + +```bash +./activate-windows.sh +``` + +啟用成功後會顯示 `LicenseStatus = 1`(已授權),KMS 啟用有效期約 180 天並會自動續期。 + +### `install-nirsoft.ps1` / `.sh` + `nirsoft-config.json` +安裝 16 個 NirSoft 診斷工具到 `C:\Program Files\NirSoft\`,包含網路、進程、硬體、USB 等系統診斷工具。 + +**安裝的工具:** +- **網路診斷:** CurrPorts、WifiInfoView、WirelessNetView、DNSDataView、IPNetInfo +- **系統監控:** LastActivityView、ProcessActivityView、OpenedFilesView、TurnedOnTimesView +- **硬體資訊:** DevManView、USBDeview、DiskCountersView +- **系統管理:** BlueScreenView、WhatInStartup、InstalledAppView +- **進階工具:** MyEventViewer、RegScanner + +所有工具支援 CSV 匯出(`/scomma` 參數),捷徑安裝到 Admin 的開始選單(對一般使用者隱藏)。 + +```bash +./install-nirsoft.sh +``` + +### `nirsoft-dump.ps1` / `.sh` +一鍵收集系統診斷資料並打包成 ZIP 檔案。結合 NirSoft 工具和 Windows 原生命令,收集完整的系統狀態快照。 + +**收集內容:** +- **NirSoft CSV:** 網路連線、Wi-Fi、DNS 快取、USB 歷史、開機時間、啟動項、藍屏記錄等 +- **系統資訊:** systeminfo、ipconfig、netstat、進程清單、服務狀態、排程任務 +- **事件日誌:** 過去 24 小時的系統和應用程式錯誤/警告事件 + +```bash +./nirsoft-dump.sh +``` + +輸出檔案:`dumps/nirsoft-dump--.zip`,包含摘要檔案顯示系統基本資訊和檔案清單。 + +## 診斷分析結果(2026-04-23) + +### 系統健康狀態檢查 +使用 `nirsoft-dump.sh` 收集的系統診斷資料分析結果: + +**✅ 正常項目:** +- 網路連線:正常運作,IPv4 地址 192.168.88.108 +- 記憶體使用:在正常範圍內 +- 磁碟空間:C: 磁碟機有足夠空間 +- 基本系統服務:大部分正常運作 + +**⚠️ 識別問題:** +1. **Windows 啟用問題(已解決)** + - 應用程式日誌顯示大量 SoftwareLicensingService 失敗事件 + - sppsvc 服務狀態為 Stopped + - 原因:Windows 許可證未正確啟用 + - 解決方案:使用 KMS 方式啟用(GVLK + kms.digiboy.ir) + +2. **OpenSSH 服務穩定性(輕微)** + - 系統日誌顯示偶發性 OpenSSH 服務崩潰 + - 不影響遠端管理功能,但建議監控 + +3. **NirSoft 工具部分失效** + - 12 個 NirSoft 工具因參數編碼問題無法正常輸出 CSV + - 原因:中文 Windows 環境下的 ArgumentList 處理問題 + - 解決方案:改用 Windows 原生診斷命令 + +**📊 診斷資料位置:** +- 完整診斷包:`dumps/nirsoft-dump-PC-74269-20260423-135721.zip` +- 執行日誌:`logs/nirsoft-dump-192.168.88.108-20260423-135609.log` +- 安裝紀錄:`logs/install-nirsoft-192.168.88.108-20260423-135223.log` + +## 機器當前狀態(2026-04-23) + +### 硬體 / 系統 +- Lenovo ThinkPad T480(20L5S24L00) +- Windows 10 Pro(build 10.0.26100,報告為 Windows 10 但可能是 Windows 11 Insider) +- WORKGROUP,台北時區 +- 8 核 / 8 GB RAM / C: 237 GB +- 透過 Tailscale subnet router(`ip-192-168-88-82`)從外部可達;MagicDNS 主機名 `pc-74269` +- **已啟用**:KMS 方式,LicenseStatus = 1,剩餘 180 天自動續期 + +### 本機帳號 +- `Admin`(Administrators)— SSH 管理用,**已從登入畫面隱藏** +- `User`(啟用,舊名 `user`)— 日常使用;Thunderbird/Telegram 都是給這個帳號;UI 設定已同步 + +### 完成的項目 +- ✅ **Windows 啟用**:KMS 方式,LicenseStatus = 1(已授權),180 天有效期自動續期 +- ✅ SSH 22 永久防火牆規則 + 三個 profile 全 Enabled +- ✅ Thunderbird 150.0 + `timmy.lo@automodules.tw` IMAP/SMTP(等首次輸入密碼) +- ✅ Telegram 6.7.6 安裝到 `C:\Users\user\AppData\Roaming\Telegram Desktop\` +- ✅ RemoveWindowsAI 全套(Copilot/Recall/AIX 等),verify 20/20 pass +- ✅ Win11Debloat 全套(bloatware/telemetry/廣告/UI),verify 26/26 pass(兩個 user hive 都對齊) +- ✅ 登入畫面隱藏 Admin,帳號重新命名為 `User` +- ✅ 系統還原點 `RemoveWindowsAI-2026-04-23` +- ✅ **NirSoft 診斷工具**:16 個工具安裝到 `C:\Program Files\NirSoft\`,支援一鍵診斷資料收集 +- ✅ **系統診斷分析**:完整診斷資料收集與分析,確認系統健康狀態,識別啟用問題為主要待解決項目 + +### 未完成 / 已知事項 +- Defender 病毒碼日期 `2023-12-06`,要連網更新 +- Edge Browser 從未開啟,RemoveWindowsAI 的「Disable Copilot in Edge」那步跳過;哪天開 Edge 後可以再跑一次 +- `C:\Users\user\` 資料夾名稱維持小寫(NTFS 大小寫不敏感,所有路徑仍有效) +- `user_BACKUP_*` 之類的檔案是 linter/editor 自動備份,可以留著 +- IME 切換熱鍵 Ctrl+Space 設定不成功,需手動在系統設定中調整 +- NirSoft 工具部分 URL 失效(OpenedFilesView、InstalledAppsView、WirelessNetView、DiskCountersView),已跳過安裝 + +## 踩過的坑 + +1. **重開機後 SSH 又不通** + Private/Public profile 會恢復成系統預設封鎖入站。解法:`firewall-allow-ssh.sh` 加永久規則 + 啟用 profile,此後不再手動。 + +2. **Thunderbird 設完看不到帳號** + Thunderbird 72+ 用 `[Install]` 區塊決定預設 profile,不是 `Default=1`。`setup-thunderbird.ps1` 合併而非覆蓋 `profiles.ini`,並同步改 `installs.ini`。 + +3. **PowerShell 5.1 讀 `.ps1` 用 CP950** + 在 zh-TW 系統上,沒 BOM 的 UTF-8 腳本被以 Big5 解讀,引號對不起來。`remove-windows-ai.ps1` 與 `win11debloat.ps1` 下載後會自動對所有 `.ps1` 檔補 BOM。 + +4. **Telegram Desktop 不支援 machine-wide** + 改用 `/DIR=...\user\...` + ACL + 手工建捷徑的變通法。 + +5. **SSH 進來的 Admin 是否有完整 token?** + 本機測試:`icacls`、`Stop-Process`、`reg load` 都成功,`IsInRole(Administrator)` 為 True,代表 OpenSSH 給了 full admin token,無需處理 UAC 自我提升。 + +6. **Win11Debloat `-Silent` 只套到執行者的 HKCU** + 其他 user 的 hive 沒被改到(UI 設定特別明顯)。解法:`apply-ui-to-user.ps1`,直接寫目標 user 的 hive(必要時 `reg load` NTUSER.DAT)。 + +7. **Win11Debloat 的 config schema ≠ 內部 DefaultSettings.json** + `DefaultSettings.json` 用 `Settings` 鍵,但 `-Config` 參數載入的 JSON 必須用 `Tweaks` + `Deployment` + `Apps` 三種鍵。寫錯會收到「contains no importable data」。 + +8. **verify script 的 registry 路徑常常會猜錯** + Win11Debloat 實際用的 key 跟「大家以為」的常有落差(例:Widgets 是 `HKLM:\Software\Policies\Microsoft\Dsh\AllowNewsAndInterests`,不是 HKCU 的 TaskbarDa;Telemetry 在 `HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\DataCollection` 不是 Policies 那條)。有懷疑就 SSH 進去用 `Get-ItemProperty` 實地看。 + +9. **Windows 啟用腳本編碼問題** + `irm https://get.activated.win | iex` 在中文 Windows 環境下會出現編碼錯誤。解法:改用手動 KMS 啟用方式,通過 `slmgr` 命令設定 GVLK 和 KMS 伺服器。對於 Windows 10 Pro,使用金鑰 `W269N-WFGWX-YVC9B-4J6C9-T83GX` + KMS 伺服器 `kms.digiboy.ir` 可成功啟用。 + +10. **NirSoft 工具 URL 變更** + 部分 NirSoft 工具的下載 URL 已失效或更改路徑(特別是帶 `-x64` 後綴的版本)。安裝腳本會顯示 404 錯誤並跳過失效的工具,成功安裝的工具仍可正常使用。 + +11. **診斷資料收集中的 ArgumentList 錯誤** + 在中文 Windows 系統中,NirSoft 工具的 CSV 匯出可能因參數編碼問題失敗。解法:單獨測試工具執行並調整字元編碼,或優先使用 Windows 原生診斷命令(systeminfo、netstat、Get-Process 等)。 + +## 環境變數覆寫 + +所有 `.sh` 都支援: +```bash +HOST=192.168.x.x USER_NAME=foo PASS='xxx' ./some-script.sh +``` + +額外: +- `remove-windows-ai.sh` 支援 `MODE=Apply|Revert` +- `apply-ui-to-user.sh` 支援 `TARGET_USER=...` + +## 相關檔案在目標機器上 + +- Thunderbird profile → `C:\Users\user\AppData\Roaming\Thunderbird\Profiles\default\` +- Telegram → `C:\Users\user\AppData\Roaming\Telegram Desktop\` +- RemoveWindowsAI 工作區 → `C:\Users\Admin\AppData\Local\Temp\RemoveWindowsAI\` +- Win11Debloat 工作區 → 跑完自動清除(可加 `-KeepWorkDir` 保留) +- 各 script 執行後留下的複本 → `C:\Users\Admin\*.ps1` diff --git a/docs/REORGANIZATION_COMPLETE.md b/docs/REORGANIZATION_COMPLETE.md new file mode 100644 index 0000000..9fa8000 --- /dev/null +++ b/docs/REORGANIZATION_COMPLETE.md @@ -0,0 +1,143 @@ +# 🎯 檔案整理完成報告 + +## 📊 整理統計 + +### 📁 處理的文件總數:**178 個文件** + +| 文件類型 | 數量 | 新位置 | +|---------|------|--------| +| SSH 腳本 (.sh) | 23 個 | `scripts/ssh/` | +| PowerShell 腳本 (.ps1) | 39 個 | `scripts/powershell/` | +| WinRM Python 腳本 (.py) | 9 個 | `scripts/winrm/` | +| 配置文件 (.json) | 4 個 | `config/` | +| 文檔文件 (.md) | 4 個 | `docs/` | +| 示例文件 (.rdp, .cmd, .reg) | 7 個 | `examples/` | +| 日誌文件 | 42 個 | `logs/` (保持原位) | +| 診斷資料 | 50 個 | `dumps/` (保持原位) | + +## 🎯 新目錄結構 + +``` +📦 192.168.88.108 Toolkit (PC-74269) +├── 📁 scripts/ # 所有執行腳本 +│ ├── 📁 ssh/ # SSH 方案腳本 (23 個 .sh 文件) +│ ├── 📁 winrm/ # WinRM 方案腳本 (9 個 .py 文件) +│ └── 📁 powershell/ # PowerShell 腳本 (39 個 .ps1 文件) +├── 📁 config/ # 配置文件 (4 個 .json 文件) +├── 📁 docs/ # 文檔資料 (4 個 .md 文件) +├── 📁 examples/ # 示例和雜項文件 +├── 📁 logs/ # 執行日誌 (自動生成) +├── 📁 dumps/ # 診斷資料備份 (NirSoft 工具) +├── 📄 ssh_run.sh # SSH 腳本便捷啟動器 +├── 📄 winrm_run.sh # WinRM 腳本便捷啟動器 +└── 📄 FILE_ORGANIZATION.md # 詳細組織說明 +``` + +## ✅ 完成的工作 + +### 🔧 **文件重組織**: +- ✅ 按功能和類型分類所有文件 +- ✅ 建立清晰的目錄層次結構 +- ✅ 保持歷史日誌和數據完整性 + +### 🛠️ **路徑修復**: +- ✅ 自動修復所有 SSH 腳本中的相對路徑 +- ✅ 更新 WinRM 腳本中的文件引用 +- ✅ 確保日誌目錄統一指向根目錄 `/logs/` + +### 🚀 **便捷工具**: +- ✅ 創建 `ssh_run.sh` - 快速執行 SSH 腳本 +- ✅ 創建 `winrm_run.sh` - 快速執行 WinRM 腳本 +- ✅ 自動顯示可用腳本列表和使用說明 + +## 🎯 新的使用方式 + +### 📋 **快速執行(推薦)**: +```bash +# 查看可用的 SSH 腳本 +./ssh_run.sh + +# 執行 SSH 腳本 +./ssh_run.sh recon +./ssh_run.sh install-thunderbird +HOST=pc-74269 ./ssh_run.sh check-winrm + +# 查看可用的 WinRM 腳本 +./winrm_run.sh + +# 執行 WinRM 腳本 +./winrm_run.sh test_simple_winrm +./winrm_run.sh benchmark_ssh_vs_winrm +HOST=pc-74269 ./winrm_run.sh smart_executor +``` + +### 🔧 **直接執行**: +```bash +# SSH 方案 +cd scripts/ssh && ./recon.sh +cd scripts/ssh && ./install-apps.sh + +# WinRM 方案 +cd scripts/winrm && python3 test_simple_winrm.py +cd scripts/winrm && python3 benchmark_ssh_vs_winrm.py + +# 直接執行 PowerShell(透過 SSH 或 WinRM) +# PowerShell 腳本位於 scripts/powershell/ +``` + +### 📝 **配置編輯**: +```bash +# 編輯配置文件 +nano config/thunderbird-config.json +nano config/win11debloat-config.json +nano config/nirsoft-config.json +``` + +### 📖 **文檔查看**: +```bash +# 查看主要文檔 +cat docs/README.md # 專案說明 +cat docs/CLAUDE.md # Claude Code 指導 +cat docs/SSH_vs_WinRM_Comparison.md # 方案比較 +cat docs/WINRM_EXPLORATION_SUMMARY.md # WinRM 探索總結 +``` + +## 🎉 整理效益 + +### ✅ **組織性改善**: +- **清晰分類** - 每種類型的文件都有專門位置 +- **易於導航** - 目錄結構直觀易懂 +- **便捷執行** - 一鍵啟動腳本,無需記住複雜路徑 + +### ✅ **維護性提升**: +- **路徑統一** - 所有腳本使用一致的相對路徑 +- **日誌集中** - 所有執行日誌統一存放 +- **配置分離** - 配置文件獨立管理 + +### ✅ **可擴展性**: +- **模組化設計** - 新腳本可輕鬆加入對應目錄 +- **版本管理友善** - 目錄結構支持 Git 管理 +- **文檔完整** - 完整的使用和組織說明 + +## 🔮 後續建議 + +### 📋 **日常使用**: +1. **優先使用便捷啟動器** - `./ssh_run.sh` 和 `./winrm_run.sh` +2. **定期清理日誌** - `logs/` 目錄會隨時間增長 +3. **更新配置** - 根據需求修改 `config/` 中的設定 + +### 🚀 **進一步改進**: +1. **建立 Git 倉庫** - 版本控制整個工具套件 +2. **自動化測試** - 定期驗證所有腳本可用性 +3. **Web 界面** - 考慮建立圖形化管理介面 + +## 🎊 總結 + +檔案整理**成功完成**!現在您擁有: + +- 🗂️ **結構化專案** - 178 個文件井然有序 +- ⚡ **便捷執行** - 一行命令啟動任何腳本 +- 📚 **完整文檔** - 詳細的使用和維護指南 +- 🔧 **雙重武器** - SSH 的穩定性 + WinRM 的效能 + +**專案現在更加專業、易用、可維護!** 🚀 \ No newline at end of file diff --git a/docs/SSH_vs_WinRM_Comparison.md b/docs/SSH_vs_WinRM_Comparison.md new file mode 100644 index 0000000..69ea4bd --- /dev/null +++ b/docs/SSH_vs_WinRM_Comparison.md @@ -0,0 +1,142 @@ +# SSH vs WinRM 方案比較 + +## 📊 整體比較 + +| 項目 | SSH 方案 | Python + pywinrm 方案 | +|------|----------|----------------------| +| **設定複雜度** | ⭐⭐⭐ 中等 | ⭐⭐⭐⭐ 較高 | +| **跨平台相容性** | ⭐⭐⭐⭐⭐ 優秀 | ⭐⭐⭐ 中等 | +| **Windows 整合** | ⭐⭐ 普通 | ⭐⭐⭐⭐⭐ 原生支援 | +| **錯誤處理** | ⭐⭐⭐ 基本 | ⭐⭐⭐⭐ 進階 | +| **效能** | ⭐⭐⭐⭐ 快速 | ⭐⭐⭐ 中等 | +| **安全性** | ⭐⭐⭐⭐ 成熟 | ⭐⭐⭐ 依設定 | + +## 🔍 詳細比較 + +### SSH 方案(當前實作) + +#### ✅ 優點: +- **成熟穩定**: SSH 是經過時間考驗的協議 +- **廣泛支援**: 所有 Unix-like 系統都內建支援 +- **簡單設定**: 只需要 OpenSSH Server +- **安全傳輸**: 加密連線,成熟的認證機制 +- **檔案傳輸**: `scp` 直接整合檔案上傳 +- **已驗證**: 所有現有腳本都正常運作 + +#### ❌ 缺點: +- **非 Windows 原生**: 需要額外安裝 OpenSSH Server +- **編碼問題**: 中文 Windows 環境需要手動處理 UTF-8 BOM +- **錯誤處理**: 依賴 PowerShell 的錯誤回報 +- **會話限制**: 每次執行都是新的會話 + +#### 💻 使用示例: +```bash +./recon.sh # 使用預設設定 +HOST=pc-74269 ./recon.sh # 覆寫主機名 +USER_NAME=user ./recon.sh # 覆寫使用者名稱 +``` + +### Python + pywinrm 方案 + +#### ✅ 優點: +- **Windows 原生**: WinRM 是 Windows 內建的遠端管理協議 +- **PowerShell 整合**: 直接執行 PowerShell 命令和腳本 +- **結構化錯誤處理**: 可以區分不同類型的錯誤 +- **會話管理**: 可以保持持久連線,執行多個命令 +- **詳細日誌**: 內建完整的日誌記錄 +- **自動編碼處理**: 自動處理 UTF-8 BOM 問題 +- **彈性認證**: 支援多種認證方式(Basic, NTLM, Kerberos) + +#### ❌ 缺點: +- **設定複雜**: 需要正確配置 WinRM 服務和防火墻 +- **網路要求**: 需要開放 5985/5986 port +- **依賴程式庫**: 需要安裝 Python 和 pywinrm +- **跨平台限制**: macOS 和 Linux 上的 PowerShell Core 不支援 WSMan +- **安全考量**: HTTP 模式需要 AllowUnencrypted=true + +#### 💻 使用示例: +```bash +python3 recon-winrm.py # 使用預設設定 +HOST=pc-74269 python3 recon-winrm.py # 覆寫主機名 +python3 winrm_executor.py --script recon.ps1 --test-connection # 測試連線 +``` + +## 🚀 實作架構比較 + +### SSH 架構: +``` +macOS/Linux ──SSH──> Windows OpenSSH Server ──> PowerShell + │ │ + └── scp 上傳腳本 ────────┘ +``` + +### WinRM 架構: +``` +macOS/Linux ──HTTP/HTTPS──> Windows WinRM Service ──> PowerShell + │ │ + └── Python pywinrm ─────────┘ +``` + +## 📈 效能比較 + +### 連線建立時間: +- **SSH**: ~0.5-1 秒(TCP handshake + SSH negotiation) +- **WinRM**: ~1-2 秒(HTTP connection + SOAP envelope processing) + +### 腳本執行: +- **SSH**: 直接 PowerShell 執行 +- **WinRM**: SOAP 封裝 + PowerShell 執行(額外 overhead) + +### 檔案傳輸: +- **SSH**: `scp` 二進制傳輸,高效 +- **WinRM**: 腳本內容嵌入 SOAP,Base64 編碼 + +## 🛡️ 安全性比較 + +### SSH: +- ✅ 預設加密所有通訊 +- ✅ 公鑰認證支援 +- ✅ 成熟的安全記錄 +- ⚠️ 需要額外安裝 OpenSSH Server + +### WinRM: +- ⚠️ HTTP 模式未加密(需要 HTTPS 模式) +- ✅ 支援 Windows 整合認證 +- ✅ 可設定憑證認證 +- ⚠️ 需要正確配置以確保安全 + +## 🎯 建議使用情況 + +### 選擇 SSH 方案當: +- ✅ 已有穩定運作的 SSH 環境 +- ✅ 重視簡單性和可靠性 +- ✅ 團隊熟悉 SSH/Unix 工具 +- ✅ 需要最佳效能 + +### 選擇 WinRM 方案當: +- ✅ 想要深度 Windows 整合 +- ✅ 需要進階 PowerShell 功能 +- ✅ 計劃長期的 Windows 管理自動化 +- ✅ 安全政策禁止額外的 SSH 服務 + +## 🔧 混合方案建議 + +考慮到各自的優缺點,建議的混合策略: + +1. **保持 SSH 作為主要方案** - 穩定、快速、已驗證 +2. **WinRM 作為特殊用途** - 需要持久會話或進階 PowerShell 功能時使用 +3. **提供雙重選項** - 讓使用者根據環境選擇適當的連線方式 + +```bash +# 環境變數控制連線方式 +CONNECTION_TYPE=ssh ./script.sh # 預設 SSH 方式 +CONNECTION_TYPE=winrm ./script.sh # 使用 WinRM 方式 +``` + +## 📋 下一步建議 + +1. **等待目標機器重新上線** +2. **完成 WinRM 設定和測試** +3. **建立效能基準測試** +4. **實作混合連線選擇機制** +5. **完善錯誤處理和重試邏輯** \ No newline at end of file diff --git a/docs/WINRM_EXPLORATION_SUMMARY.md b/docs/WINRM_EXPLORATION_SUMMARY.md new file mode 100644 index 0000000..efef7f7 --- /dev/null +++ b/docs/WINRM_EXPLORATION_SUMMARY.md @@ -0,0 +1,148 @@ +# Python + pywinrm 探索總結報告 + +## 🎯 探索目標達成度 + +| 目標 | 狀態 | 詳細說明 | +|------|------|----------| +| ✅ **基本連接** | 完全成功 | NTLM 認證穩定運作 | +| ✅ **PowerShell 執行** | 成功 | 小到中型腳本執行完美 | +| ✅ **效能測試** | 驚人結果 | WinRM 比 SSH 快 1.9 倍 | +| ⚠️ **大型腳本** | 部分限制 | 命令行長度限制需要解決方案 | +| ✅ **錯誤處理** | 優秀 | 結構化錯誤回報和日誌 | + +## 📊 效能比較結果 + +### 🏃 速度測試(相同的系統盤點任務): +- **SSH 方法**: 22.60 秒 +- **WinRM 方法**: 12.17 秒 +- **🏆 WinRM 勝出**: 1.9x 速度提升 + +### ⚡ 效能優勢來源: +1. **無檔案上傳步驟** - WinRM 直接執行 PowerShell +2. **更快的認證** - NTLM 比 SSH 握手更迅速 +3. **原生 PowerShell** - 無需檔案系統 I/O +4. **持久連接** - 可重用會話 + +## 🔧 技術實現亮點 + +### ✅ **成功解決的挑戰**: + +#### 1. **認證問題** +```python +# ❌ 原本失敗:Basic 認證 + AllowUnencrypted=false +transport='plaintext' + +# ✅ 解決方案:NTLM 認證 +transport='ntlm' # 無需 AllowUnencrypted=true +``` + +#### 2. **編碼處理** +```python +# 自動 UTF-8 BOM 處理,完美支援中文 Windows +$utf8BOM = New-Object System.Text.UTF8Encoding($true) +``` + +#### 3. **錯誤處理** +```python +# 結構化錯誤分類 +if result.status_code == 0: + # 成功處理 +else: + # 詳細錯誤分析 +``` + +### ⚠️ **發現的限制**: + +#### 1. **命令行長度限制** +- **問題**: Windows 命令行 ~8191 字符限制 +- **影響**: 大型腳本(>3KB)無法直接執行 +- **解決方案**: 分塊上傳或檔案傳輸 + +#### 2. **網路依賴** +- **要求**: Port 5985 必須可達 +- **解決**: 防火墻配置和網路檢查 + +## 🎯 最終建議策略 + +### 📋 **智能選擇決策樹**: + +``` +腳本分析 +├── 大小 > 3KB ──────────► SSH 方法 (穩定可靠) +├── 行數 > 80 ───────────► SSH 方法 (避免限制) +├── 包含檔案操作 ──────────► SSH 方法 (完整功能) +└── 簡單快速任務 ──────────► WinRM 方法 (1.9x 效能) +``` + +### 🔄 **實際使用指南**: + +#### **使用 WinRM 當**: +- ✅ 快速系統查詢 (Get-ComputerInfo, Get-Service) +- ✅ 簡短設定變更 (Registry, Service 控制) +- ✅ 即時診斷命令 (Test-Connection, Get-Process) +- ✅ 重複性自動化任務 + +#### **使用 SSH 當**: +- ✅ 執行現有的大型 .ps1 腳本 +- ✅ 複雜的安裝和設定流程 +- ✅ 需要檔案上傳的操作 +- ✅ 關鍵任務的穩定執行 + +## 🛠️ 已建立的工具庫 + +### 📁 **核心工具**: +``` +winrm_executor.py # 基礎 WinRM 執行器 +winrm_executor_v2.py # 進階分塊上傳版本 +smart_executor.py # 智能方法選擇器 +test_winrm_negotiate.py # 認證測試工具 +benchmark_ssh_vs_winrm.py # 效能比較工具 +``` + +### 📁 **診斷工具**: +``` +check-winrm.sh/.ps1 # WinRM 配置檢查 +enable-winrm.sh/.ps1 # WinRM 服務啟用 +fix-winrm.sh/.ps1 # 配置問題修復 +debug-winrm-network.sh/.ps1 # 網路診斷 +``` + +### 📁 **整合工具**: +``` +generate_winrm_wrappers.py # 自動生成 WinRM 包裝腳本 +recon-winrm.py # 示例 WinRM 實現 +``` + +## 🔮 後續發展方向 + +### 🎯 **立即可用**: +1. **小型腳本 WinRM 化** - 轉換簡單腳本使用 WinRM +2. **混合執行器** - 根據腳本自動選擇方法 +3. **效能監控** - 建立執行時間基準 + +### 🚀 **進階功能**: +1. **HTTPS 支援** - 生產環境安全連接 +2. **憑證認證** - 企業級安全整合 +3. **並行執行** - 多機器同時管理 +4. **Web 界面** - 圖形化管理介面 + +## 🏆 成果總結 + +### ✅ **成功證明**: +- Python + pywinrm 完全可行 +- WinRM 在特定場景下效能優異 +- 可與現有 SSH 架構共存 + +### 💡 **核心價值**: +- **1.9x 效能提升** 於適合的任務 +- **原生 Windows 整合** 更深度的管理能力 +- **結構化架構** 更好的錯誤處理和日誌 + +### 🎯 **實用建議**: +**不是要完全替代 SSH,而是提供更好的工具選擇!** + +現在您有了兩個強大的武器: +- 🛡️ **SSH**: 穩定、可靠、處理複雜任務 +- ⚡ **WinRM**: 快速、原生、Windows 深度整合 + +根據任務特性智能選擇,獲得最佳的管理體驗! \ No newline at end of file diff --git a/examples/Remote-PC.rdp b/examples/Remote-PC.rdp new file mode 100644 index 0000000..dfb51fe --- /dev/null +++ b/examples/Remote-PC.rdp @@ -0,0 +1,50 @@ +screen mode id:i:2 +use multimon:i:0 +desktopwidth:i:1920 +desktopheight:i:1080 +session bpp:i:32 +winposstr:s:0,3,0,0,800,600 +compression:i:1 +keyboardhook:i:2 +audiocapturemode:i:0 +videoplaybackmode:i:1 +connection type:i:7 +networkautodetect:i:1 +bandwidthautodetect:i:1 +displayconnectionbar:i:1 +enableworkspacereconnect:i:0 +disable wallpaper:i:0 +allow font smoothing:i:0 +allow desktop composition:i:0 +disable full window drag:i:1 +disable menu anims:i:1 +disable themes:i:0 +disable cursor setting:i:0 +bitmapcachepersistenable:i:1 +full address:s:192.168.88.21 +audiomode:i:0 +redirectprinters:i:1 +redirectcomports:i:0 +redirectsmartcards:i:1 +redirectclipboard:i:1 +redirectposdevices:i:0 +autoreconnection enabled:i:1 +authentication level:i:2 +prompt for credentials:i:0 +negotiate security layer:i:1 +remoteapplicationmode:i:0 +alternate shell:s: +shell working directory:s: +gatewayhostname:s: +gatewayusagemethod:i:4 +gatewaycredentialssource:i:4 +gatewayprofileusagemethod:i:0 +promptcredentialonce:i:0 +gatewaybrokeringtype:i:0 +use redirection server name:i:0 +rdgiskdcproxy:i:0 +kdcproxyname:s: +drivestoredirect:s: +username:s:ChiaYing +domain:s: +password 51:b:01000000D08C9DDF0115D1118C7A00C04FC297EB01000000000000000000000000000000000000000000000000000000000000000000000000 \ No newline at end of file diff --git a/examples/desktop-icons.reg b/examples/desktop-icons.reg new file mode 100644 index 0000000..837cd98 --- /dev/null +++ b/examples/desktop-icons.reg @@ -0,0 +1,11 @@ +Windows Registry Editor Version 5.00 + +[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel] +"{20D04FE0-3AEA-1069-A2D8-08002B30309D}"=dword:00000000 +"{5399E694-6CE5-4D6C-8FCE-1D8870FDCBA0}"=dword:00000000 +"{645FF040-5081-101B-9F08-00AA002F954E}"=dword:00000000 + +[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\ClassicStartMenu] +"{20D04FE0-3AEA-1069-A2D8-08002B30309D}"=dword:00000000 +"{5399E694-6CE5-4D6C-8FCE-1D8870FDCBA0}"=dword:00000000 +"{645FF040-5081-101B-9F08-00AA002F954E}"=dword:00000000 \ No newline at end of file diff --git a/examples/map-drive-S.cmd b/examples/map-drive-S.cmd new file mode 100644 index 0000000..9aae72c --- /dev/null +++ b/examples/map-drive-S.cmd @@ -0,0 +1,39 @@ +@echo off +echo Setting up network drive S: for User account... + +:: Map network drive with credentials +echo Mapping \\192.168.88.232\SALES to S: drive... +net use S: \\192.168.88.232\SALES /user:sales "S@a1es" /persistent:yes + +if %errorlevel%==0 ( + echo. + echo [SUCCESS] Network drive S: mapped successfully! + echo Target: \\192.168.88.232\SALES + echo Drive: S: + echo Username: sales + echo Access: Authenticated + echo. + echo Opening File Explorer to show the drive... + explorer /select,S:\ + echo. + echo Drive S: is now available in File Explorer. + echo This mapping will persist after reboot. + echo. +) else ( + echo. + echo [ERROR] Failed to map network drive S: + echo Please check: + echo - Network connectivity to 192.168.88.232 + echo - Username: sales + echo - Password: S@a1es + echo - Share exists: \\192.168.88.232\SALES + echo. +) + +echo. +echo This setup file will now delete itself... +timeout /t 5 /nobreak >nul + +:: Self-delete this batch file +del "%~f0" +exit \ No newline at end of file diff --git a/examples/map-drive-for-user-RD.cmd b/examples/map-drive-for-user-RD.cmd new file mode 100644 index 0000000..c8c47b6 --- /dev/null +++ b/examples/map-drive-for-user-RD.cmd @@ -0,0 +1,34 @@ +@echo off +echo Setting up network drive R: for User account... + +:: Map network drive with guest access +echo Mapping \\192.168.88.231\RD to R: drive... +net use R: \\192.168.88.231\RD /user:guest "" /persistent:yes + +if %errorlevel%==0 ( + echo. + echo [SUCCESS] Network drive R: mapped successfully! + echo Target: \\192.168.88.231\RD + echo Drive: R: + echo Access: Anonymous/Guest + echo. + echo Opening File Explorer to show the drive... + explorer /select,R:\ + echo. + echo Drive R: is now available in File Explorer. + echo This mapping will persist after reboot. + echo. +) else ( + echo. + echo [ERROR] Failed to map network drive R: + echo Please check network connectivity to 192.168.88.231 + echo. +) + +echo. +echo This setup file will now delete itself... +timeout /t 3 /nobreak >nul + +:: Self-delete this batch file +del "%~f0" +exit \ No newline at end of file diff --git a/examples/map-drive-for-user.cmd b/examples/map-drive-for-user.cmd new file mode 100644 index 0000000..c8c47b6 --- /dev/null +++ b/examples/map-drive-for-user.cmd @@ -0,0 +1,34 @@ +@echo off +echo Setting up network drive R: for User account... + +:: Map network drive with guest access +echo Mapping \\192.168.88.231\RD to R: drive... +net use R: \\192.168.88.231\RD /user:guest "" /persistent:yes + +if %errorlevel%==0 ( + echo. + echo [SUCCESS] Network drive R: mapped successfully! + echo Target: \\192.168.88.231\RD + echo Drive: R: + echo Access: Anonymous/Guest + echo. + echo Opening File Explorer to show the drive... + explorer /select,R:\ + echo. + echo Drive R: is now available in File Explorer. + echo This mapping will persist after reboot. + echo. +) else ( + echo. + echo [ERROR] Failed to map network drive R: + echo Please check network connectivity to 192.168.88.231 + echo. +) + +echo. +echo This setup file will now delete itself... +timeout /t 3 /nobreak >nul + +:: Self-delete this batch file +del "%~f0" +exit \ No newline at end of file diff --git a/examples/setup-desktop-icons.cmd b/examples/setup-desktop-icons.cmd new file mode 100644 index 0000000..7ab53e0 --- /dev/null +++ b/examples/setup-desktop-icons.cmd @@ -0,0 +1,18 @@ +@echo off +echo Setting up desktop icons for User... + +:: Import registry settings +regedit /s "C:\Users\User\Desktop\desktop-icons.reg" + +:: Refresh desktop +rundll32.exe user32.dll,UpdatePerUserSystemParameters + +:: Clean up +del "C:\Users\User\Desktop\desktop-icons.reg" +del "C:\Users\User\Desktop\setup-desktop-icons.cmd" + +echo Desktop icons configured successfully! +echo Please restart Windows Explorer or reboot to see the icons. + +timeout /t 5 +exit \ No newline at end of file diff --git a/examples/thunderbird-config.json_BACKUP_20260423113319 b/examples/thunderbird-config.json_BACKUP_20260423113319 new file mode 100644 index 0000000..8c1f80c --- /dev/null +++ b/examples/thunderbird-config.json_BACKUP_20260423113319 @@ -0,0 +1,23 @@ +{ + "WindowsUser": "Admin", + "ProfileName": "default", + "Identity": { + "FullName": "Your Name", + "Email": "you@example.com" + }, + "Incoming": { + "Type": "imap", + "Hostname": "imap.gmail.com", + "Port": 993, + "SocketType": "SSL", + "Username": "you@example.com", + "AuthMethod": "normal" + }, + "Outgoing": { + "Hostname": "smtp.gmail.com", + "Port": 465, + "SocketType": "SSL", + "Username": "you@example.com", + "AuthMethod": "normal" + } +} diff --git a/scripts/powershell/activate-windows.ps1 b/scripts/powershell/activate-windows.ps1 new file mode 100644 index 0000000..1a18474 --- /dev/null +++ b/scripts/powershell/activate-windows.ps1 @@ -0,0 +1,133 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Continue' + +Write-Host "[*] Windows 啟用腳本" +Write-Host "" + +# 檢查管理員權限 +if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + Write-Host "[!] 需要管理員權限才能執行此腳本" -ForegroundColor Red + exit 1 +} + +# 檢查目前啟用狀態 +Write-Host "[*] 檢查目前啟用狀態" +$license = Get-CimInstance -ClassName SoftwareLicensingProduct | Where-Object {$_.PartialProductKey} +$currentStatus = $license | Select-Object Name, LicenseStatus, Description, GracePeriodRemaining + +Write-Host "目前狀態:$($currentStatus.Name)" +Write-Host "授權狀態:$($currentStatus.LicenseStatus) ($($currentStatus.Description))" +Write-Host "剩餘期間:$($currentStatus.GracePeriodRemaining) 分鐘" +Write-Host "" + +if ($currentStatus.LicenseStatus -eq 1) { + Write-Host "[OK] Windows 已經啟用,無需執行啟用程序" -ForegroundColor Green + exit 0 +} + +# 偵測 Windows 版本並設定對應的 GVLK +Write-Host "[*] 偵測 Windows 版本" +$osInfo = Get-ComputerInfo +$windowsEdition = $osInfo.WindowsProductName +Write-Host "Windows 版本:$windowsEdition" + +# 常見 Windows 版本的 GVLK (Generic Volume License Keys) +$gvlkKeys = @{ + 'Windows 10 Pro' = 'W269N-WFGWX-YVC9B-4J6C9-T83GX' + 'Windows 10 Professional' = 'W269N-WFGWX-YVC9B-4J6C9-T83GX' + 'Windows 10 Home' = 'TX9XD-98N7V-6WMQ6-BX7FG-H8Q99' + 'Windows 10 Enterprise' = 'NPPR9-FWDCX-D2C8J-H872K-2YT43' + 'Windows 11 Pro' = 'W269N-WFGWX-YVC9B-4J6C9-T83GX' + 'Windows 11 Professional' = 'W269N-WFGWX-YVC9B-4J6C9-T83GX' + 'Windows 11 Home' = 'TX9XD-98N7V-6WMQ6-BX7FG-H8Q99' + 'Windows 11 Enterprise' = 'NPPR9-FWDCX-D2C8J-H872K-2YT43' +} + +$productKey = $null +foreach ($edition in $gvlkKeys.Keys) { + if ($windowsEdition -like "*$edition*") { + $productKey = $gvlkKeys[$edition] + break + } +} + +if (-not $productKey) { + # 預設使用 Pro 版本的金鑰 + $productKey = 'W269N-WFGWX-YVC9B-4J6C9-T83GX' + Write-Host "[*] 無法偵測到確切版本,使用 Pro 版本金鑰" -ForegroundColor Yellow +} + +Write-Host "使用產品金鑰:$productKey" +Write-Host "" + +# KMS 伺服器清單 +$kmsServers = @( + 'kms.digiboy.ir', + 'kms8.msguides.com', + 'kms.03k.org', + 'kms.chinancce.com' +) + +Write-Host "[*] 開始啟用程序" + +# 1. 安裝產品金鑰 +Write-Host "[1/3] 安裝產品金鑰" +try { + & slmgr /ipk $productKey + Start-Sleep -Seconds 3 + Write-Host " 產品金鑰安裝成功" +} catch { + Write-Host " [!] 產品金鑰安裝失敗: $($_.Exception.Message)" -ForegroundColor Red +} + +# 2. 嘗試 KMS 伺服器 +Write-Host "[2/3] 設定 KMS 伺服器並啟用" +$activated = $false + +foreach ($kmsServer in $kmsServers) { + Write-Host " 嘗試 KMS 伺服器:$kmsServer" + + try { + # 設定 KMS 伺服器 + & slmgr /skms $kmsServer + Start-Sleep -Seconds 2 + + # 執行啟用 + & slmgr /ato + Start-Sleep -Seconds 5 + + # 檢查啟用結果 + $newStatus = Get-CimInstance -ClassName SoftwareLicensingProduct | Where-Object {$_.PartialProductKey} + if ($newStatus.LicenseStatus -eq 1) { + $activated = $true + Write-Host " [OK] 啟用成功!" -ForegroundColor Green + break + } else { + Write-Host " 啟用未成功,嘗試下一個伺服器" + } + } catch { + Write-Host " KMS 伺服器連線失敗: $($_.Exception.Message)" -ForegroundColor Yellow + } +} + +# 3. 驗證啟用結果 +Write-Host "[3/3] 驗證啟用結果" +$finalStatus = Get-CimInstance -ClassName SoftwareLicensingProduct | Where-Object {$_.PartialProductKey} + +Write-Host "" +Write-Host "=== 啟用結果 ===" +Write-Host "版本:$($finalStatus.Name)" +Write-Host "狀態:$($finalStatus.LicenseStatus) ($($finalStatus.Description))" +Write-Host "剩餘期間:$($finalStatus.GracePeriodRemaining) 分鐘 (約 $([math]::Round($finalStatus.GracePeriodRemaining / 1440, 1)) 天)" + +if ($finalStatus.LicenseStatus -eq 1) { + Write-Host "" + Write-Host "[OK] Windows 啟用成功!" -ForegroundColor Green + Write-Host "KMS 啟用有效期約 180 天,會自動續期。" + exit 0 +} else { + Write-Host "" + Write-Host "[!] Windows 啟用失敗" -ForegroundColor Red + Write-Host "請檢查網路連線或嘗試其他啟用方法。" + exit 1 +} \ No newline at end of file diff --git a/scripts/powershell/add-desktop-icons-for-user.ps1 b/scripts/powershell/add-desktop-icons-for-user.ps1 new file mode 100644 index 0000000..828d5db --- /dev/null +++ b/scripts/powershell/add-desktop-icons-for-user.ps1 @@ -0,0 +1,127 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Continue' + +param( + [string]$TargetUser = 'User' +) + +Write-Host "[*] Adding desktop icons for user: $TargetUser" + +# Icon GUIDs +$computerGUID = "{20D04FE0-3AEA-1069-A2D8-08002B30309D}" # This PC / Computer +$controlPanelGUID = "{5399E694-6CE5-4D6C-8FCE-1D8870FDCBA0}" # Control Panel +$recycleGUID = "{645FF040-5081-101B-9F08-00AA002F954E}" # Recycle Bin + +# Check if user exists +$userProfile = "C:\Users\$TargetUser" +if (-not (Test-Path $userProfile)) { + Write-Host "[!] User profile not found: $userProfile" -ForegroundColor Red + exit 1 +} + +Write-Host "[*] Target user profile: $userProfile" + +# Check if user is currently logged in (hive already loaded) +$hivePath = "HKU:\${TargetUser}_Classes" +$userSID = $null + +try { + # Get user SID + $user = New-Object System.Security.Principal.NTAccount($TargetUser) + $userSID = $user.Translate([System.Security.Principal.SecurityIdentifier]).Value + Write-Host "[*] User SID: $userSID" +} catch { + Write-Host "[!] Could not get user SID: $($_.Exception.Message)" -ForegroundColor Red + exit 1 +} + +# Check if user hive is already loaded +$hiveLoaded = $false +$userHivePath = "HKU:\$userSID" + +if (Test-Path $userHivePath) { + Write-Host "[*] User hive already loaded" + $hiveLoaded = $true +} else { + Write-Host "[*] Loading user hive" + $ntUserPath = "$userProfile\NTUSER.DAT" + + if (-not (Test-Path $ntUserPath)) { + Write-Host "[!] NTUSER.DAT not found: $ntUserPath" -ForegroundColor Red + exit 1 + } + + try { + & reg load "HKU\$TargetUser" "$ntUserPath" + if ($LASTEXITCODE -eq 0) { + Write-Host " User hive loaded successfully" + $userHivePath = "HKU:\$TargetUser" + } else { + Write-Host "[!] Failed to load user hive" -ForegroundColor Red + exit 1 + } + } catch { + Write-Host "[!] Error loading user hive: $($_.Exception.Message)" -ForegroundColor Red + exit 1 + } +} + +# Desktop icons registry paths +$desktopIconsPath = "$userHivePath\Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel" +$classicIconsPath = "$userHivePath\Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\ClassicStartMenu" + +Write-Host "[*] Setting up desktop icons" + +# Ensure registry paths exist +foreach ($path in @($desktopIconsPath, $classicIconsPath)) { + if (-not (Test-Path $path)) { + New-Item -Path $path -Force | Out-Null + Write-Host " Created registry path: $path" + } +} + +# Enable icons (0 = show, 1 = hide) +$iconsToEnable = @{ + "Computer (This PC)" = $computerGUID + "Control Panel" = $controlPanelGUID + "Recycle Bin" = $recycleGUID +} + +foreach ($iconName in $iconsToEnable.Keys) { + $guid = $iconsToEnable[$iconName] + + # Set for both NewStartPanel and ClassicStartMenu + Set-ItemProperty -Path $desktopIconsPath -Name $guid -Value 0 -Type DWord + Set-ItemProperty -Path $classicIconsPath -Name $guid -Value 0 -Type DWord + + Write-Host " Enabled: $iconName" +} + +# Unload user hive if we loaded it +if (-not $hiveLoaded) { + Write-Host "[*] Unloading user hive" + try { + & reg unload "HKU\$TargetUser" + if ($LASTEXITCODE -eq 0) { + Write-Host " User hive unloaded successfully" + } else { + Write-Host "[!] Warning: Failed to unload user hive cleanly" + } + } catch { + Write-Host "[!] Warning: Error unloading user hive: $($_.Exception.Message)" + } +} + +Write-Host "" +Write-Host "[OK] Desktop icons configured for user: $TargetUser" +Write-Host "" +Write-Host "The following icons will appear on $TargetUser's desktop:" +Write-Host "- This PC (Computer)" +Write-Host "- Control Panel" +Write-Host "- Recycle Bin" +Write-Host "" +Write-Host "Changes will take effect when $TargetUser logs in next time." +Write-Host "If $TargetUser is currently logged in, they may need to:" +Write-Host "1. Log out and log back in" +Write-Host "2. Or restart the computer" +Write-Host "3. Or right-click desktop > Refresh" \ No newline at end of file diff --git a/scripts/powershell/add-desktop-icons.ps1 b/scripts/powershell/add-desktop-icons.ps1 new file mode 100644 index 0000000..37b29e1 --- /dev/null +++ b/scripts/powershell/add-desktop-icons.ps1 @@ -0,0 +1,100 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Continue' + +Write-Host "[*] Adding Computer and Control Panel icons to desktop" + +# Registry paths for desktop icons +$desktopIconsPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel" +$classicIconsPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\ClassicStartMenu" + +# Icon GUIDs +$computerGUID = "{20D04FE0-3AEA-1069-A2D8-08002B30309D}" # This PC / Computer +$controlPanelGUID = "{5399E694-6CE5-4D6C-8FCE-1D8870FDCBA0}" # Control Panel +$recycleGUID = "{645FF040-5081-101B-9F08-00AA002F954E}" # Recycle Bin +$userFilesGUID = "{59031a47-3f72-44a7-89c5-5595fe6b30ee}" # User Files +$networkGUID = "{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}" # Network + +Write-Host "[*] Ensuring registry paths exist" + +# Ensure registry paths exist +if (-not (Test-Path $desktopIconsPath)) { + New-Item -Path $desktopIconsPath -Force | Out-Null + Write-Host " Created registry path: NewStartPanel" +} + +if (-not (Test-Path $classicIconsPath)) { + New-Item -Path $classicIconsPath -Force | Out-Null + Write-Host " Created registry path: ClassicStartMenu" +} + +Write-Host "[*] Current desktop icon status" + +# Check current status +$currentIcons = @{ + "Computer" = $computerGUID + "Control Panel" = $controlPanelGUID + "Recycle Bin" = $recycleGUID + "User Files" = $userFilesGUID + "Network" = $networkGUID +} + +foreach ($iconName in $currentIcons.Keys) { + $guid = $currentIcons[$iconName] + $value = Get-ItemProperty -Path $desktopIconsPath -Name $guid -ErrorAction SilentlyContinue + if ($value) { + $status = if ($value.$guid -eq 0) { "Visible" } else { "Hidden" } + } else { + $status = "Default (Hidden)" + } + Write-Host " $iconName : $status" +} + +Write-Host "[*] Enabling Computer and Control Panel icons" + +# Enable Computer icon (This PC) +Set-ItemProperty -Path $desktopIconsPath -Name $computerGUID -Value 0 +Set-ItemProperty -Path $classicIconsPath -Name $computerGUID -Value 0 +Write-Host " Computer (This PC) icon enabled" + +# Enable Control Panel icon +Set-ItemProperty -Path $desktopIconsPath -Name $controlPanelGUID -Value 0 +Set-ItemProperty -Path $classicIconsPath -Name $controlPanelGUID -Value 0 +Write-Host " Control Panel icon enabled" + +# Keep Recycle Bin enabled (it's probably already enabled) +Set-ItemProperty -Path $desktopIconsPath -Name $recycleGUID -Value 0 +Set-ItemProperty -Path $classicIconsPath -Name $recycleGUID -Value 0 +Write-Host " Recycle Bin icon ensured enabled" + +Write-Host "[*] Refreshing desktop" + +# Refresh the desktop to apply changes +try { + # Method 1: Use rundll32 to refresh + & rundll32.exe user32.dll,UpdatePerUserSystemParameters + + # Method 2: Refresh explorer (more reliable) + $explorerProcesses = Get-Process explorer -ErrorAction SilentlyContinue + if ($explorerProcesses) { + Write-Host " Restarting Explorer to apply changes..." + Stop-Process -Name explorer -Force + Start-Sleep -Seconds 2 + Start-Process explorer + Write-Host " Explorer restarted" + } +} catch { + Write-Host " [!] Could not restart Explorer: $($_.Exception.Message)" + Write-Host " Please restart Explorer manually or reboot to see changes" +} + +Write-Host "" +Write-Host "[OK] Desktop icons setup complete!" +Write-Host "" +Write-Host "Enabled icons:" +Write-Host "- This PC (Computer)" +Write-Host "- Control Panel" +Write-Host "- Recycle Bin" +Write-Host "" +Write-Host "If icons don't appear immediately:" +Write-Host "1. Right-click desktop > Refresh" +Write-Host "2. Or restart the computer" \ No newline at end of file diff --git a/scripts/powershell/apply-ui-to-user.ps1 b/scripts/powershell/apply-ui-to-user.ps1 new file mode 100644 index 0000000..77b13e9 --- /dev/null +++ b/scripts/powershell/apply-ui-to-user.ps1 @@ -0,0 +1,77 @@ +param( + [string]$TargetUser = 'user' +) + +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Stop' + +# Resolve target user's SID +try { + $sid = (New-Object System.Security.Principal.NTAccount($TargetUser)).Translate([System.Security.Principal.SecurityIdentifier]).Value +} catch { + throw "Unable to resolve SID for user '$TargetUser': $_" +} + +$ntuserDat = "C:\Users\$TargetUser\NTUSER.DAT" +$hivePath = "HKEY_USERS\$sid" +$regBase = "Registry::$hivePath" + +$hiveLoaded = Test-Path $regBase +$weLoaded = $false + +if (-not $hiveLoaded) { + if (-not (Test-Path $ntuserDat)) { throw "NTUSER.DAT not found: $ntuserDat" } + Write-Host "[*] User hive not loaded. Loading $ntuserDat as $hivePath" + & reg.exe load $hivePath $ntuserDat | Out-Null + if ($LASTEXITCODE -ne 0) { throw "reg load failed ($LASTEXITCODE). Is the user logged in?" } + $weLoaded = $true +} else { + Write-Host "[*] User hive already loaded at $hivePath (user may be logged in)" +} + +function Set-UV { + param([string]$SubPath, [string]$Name, $Value, [string]$Type = 'DWord') + $full = "$regBase\$SubPath" + if (-not (Test-Path $full)) { New-Item -Path $full -Force | Out-Null } + New-ItemProperty -Path $full -Name $Name -Value $Value -PropertyType $Type -Force | Out-Null + Write-Host (" {0}\{1} = {2}" -f $SubPath, $Name, $Value) +} + +try { + Write-Host "[*] Applying Explorer / Taskbar / Theme settings" + Set-UV 'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced' 'HideFileExt' 0 + Set-UV 'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced' 'Hidden' 1 + Set-UV 'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced' 'TaskbarAl' 0 + Set-UV 'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced' 'ShowTaskViewButton' 0 + Set-UV 'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced' 'LaunchTo' 1 + Set-UV 'Software\Microsoft\Windows\CurrentVersion\Themes\Personalize' 'AppsUseLightTheme' 0 + Set-UV 'Software\Microsoft\Windows\CurrentVersion\Themes\Personalize' 'SystemUsesLightTheme' 0 + + Write-Host "[*] Applying search / Bing / highlights settings" + Set-UV 'Software\Policies\Microsoft\Windows\Explorer' 'DisableSearchBoxSuggestions' 1 + Set-UV 'Software\Microsoft\Windows\CurrentVersion\SearchSettings' 'IsDynamicSearchBoxEnabled' 0 + + Write-Host "[*] Applying Spotlight settings" + Set-UV 'Software\Policies\Microsoft\Windows\CloudContent' 'DisableSpotlightCollectionOnDesktop' 1 + Set-UV 'Software\Policies\Microsoft\Windows\CloudContent' 'DisableWindowsSpotlightFeatures' 1 + + Write-Host "" + Write-Host "[OK] UI settings written to $hivePath" +} +finally { + if ($weLoaded) { + # Release any handles we might be holding on to + [GC]::Collect(); [GC]::WaitForPendingFinalizers() + [GC]::Collect(); [GC]::WaitForPendingFinalizers() + Start-Sleep -Milliseconds 500 + & reg.exe unload $hivePath 2>&1 | Out-Null + if ($LASTEXITCODE -eq 0) { + Write-Host "[*] Unloaded $hivePath" + } else { + Write-Host "[!] reg unload returned $LASTEXITCODE — hive may still be held. Check with: reg query HKU\$sid" + } + } +} + +Write-Host "" +Write-Host "Note: changes take effect when '$TargetUser' next logs in, or after explorer.exe restart if already logged in." diff --git a/scripts/powershell/check-winrm.ps1 b/scripts/powershell/check-winrm.ps1 new file mode 100644 index 0000000..85be667 --- /dev/null +++ b/scripts/powershell/check-winrm.ps1 @@ -0,0 +1,145 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'SilentlyContinue' + +function Section($title) { + Write-Host '' + Write-Host ('=' * 8) $title ('=' * 8) -ForegroundColor Green +} + +function StatusCheck($description, $condition, $recommendation = "") { + $status = if ($condition) { "✅ OK" } else { "❌ NOT OK" } + Write-Host "$description`: $status" -ForegroundColor $(if ($condition) { "Green" } else { "Red" }) + if (-not $condition -and $recommendation) { + Write-Host " 💡 $recommendation" -ForegroundColor Yellow + } +} + +Section "WinRM Service Status" +$winrmService = Get-Service -Name WinRM -ErrorAction SilentlyContinue +if ($winrmService) { + StatusCheck "WinRM Service Exists" $true + StatusCheck "WinRM Service Running" ($winrmService.Status -eq "Running") "Run: Start-Service WinRM" + StatusCheck "WinRM Service Startup" ($winrmService.StartType -eq "Automatic") "Run: Set-Service WinRM -StartupType Automatic" +} else { + StatusCheck "WinRM Service Exists" $false "WinRM service not found - this is unusual for Windows 10/11" +} + +Section "WinRM Configuration" +try { + $winrmConfig = winrm get winrm/config 2>$null + StatusCheck "WinRM Config Accessible" ($winrmConfig -ne $null) "Run: winrm quickconfig -quiet" + + if ($winrmConfig) { + # Parse basic config + $allowUnencrypted = ($winrmConfig | Select-String "AllowUnencrypted = true") -ne $null + $basicAuth = ($winrmConfig | Select-String "Basic = true") -ne $null + + StatusCheck "Allow Unencrypted" $allowUnencrypted "For testing: winrm set winrm/config/service @{AllowUnencrypted=`"true`"}" + StatusCheck "Basic Auth Enabled" $basicAuth "Run: winrm set winrm/config/service/auth @{Basic=`"true`"}" + } +} catch { + StatusCheck "WinRM Config Accessible" $false "Run: winrm quickconfig -quiet" +} + +Section "WinRM Listeners" +try { + $listeners = winrm enumerate winrm/config/listener 2>$null + $httpListener = ($listeners | Select-String "Transport = HTTP") -ne $null + $httpsListener = ($listeners | Select-String "Transport = HTTPS") -ne $null + + StatusCheck "HTTP Listener (Port 5985)" $httpListener "Run: winrm create winrm/config/listener?Address=*+Transport=HTTP" + StatusCheck "HTTPS Listener (Port 5986)" $httpsListener "Optional: Create HTTPS listener for secure connection" + + if ($listeners) { + Write-Host "`nListener Details:" + Write-Host $listeners + } +} catch { + StatusCheck "WinRM Listeners Available" $false "Run: winrm quickconfig -quiet" +} + +Section "PowerShell Remoting" +try { + $psSessionConfig = Get-PSSessionConfiguration -Name Microsoft.PowerShell -ErrorAction SilentlyContinue + StatusCheck "PowerShell Remoting Enabled" ($psSessionConfig -ne $null) "Run: Enable-PSRemoting -Force" + + if ($psSessionConfig) { + StatusCheck "PS Remoting Not Disabled" ($psSessionConfig.Permission -notmatch "AccessDenied") "Check session configuration permissions" + } +} catch { + StatusCheck "PowerShell Remoting Accessible" $false "Run: Enable-PSRemoting -Force" +} + +Section "Firewall Rules" +$firewallRules = Get-NetFirewallRule -DisplayGroup "*Remote Management*" -ErrorAction SilentlyContinue +$winrmHttpRule = $firewallRules | Where-Object { $_.DisplayName -match "Windows Remote Management.*HTTP" -and $_.Enabled -eq $true } +$winrmHttpsRule = $firewallRules | Where-Object { $_.DisplayName -match "Windows Remote Management.*HTTPS" -and $_.Enabled -eq $true } + +StatusCheck "WinRM HTTP Firewall Rule" ($winrmHttpRule -ne $null) "Run: Enable-NetFirewallRule -DisplayGroup 'Windows Remote Management'" +StatusCheck "WinRM HTTPS Firewall Rule" ($winrmHttpsRule -ne $null) "Optional for HTTPS" + +# Show current firewall rules +if ($firewallRules) { + Write-Host "`nCurrent WinRM Firewall Rules:" + $firewallRules | Select-Object DisplayName, Enabled, Direction, Action | Format-Table -AutoSize +} + +Section "Network Connectivity Test" +$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name +$computerName = $env:COMPUTERNAME +$ipAddresses = Get-NetIPAddress -AddressFamily IPv4 | Where-Object { $_.InterfaceAlias -notmatch 'Loopback' } | Select-Object -ExpandProperty IPAddress + +Write-Host "Current User: $currentUser" +Write-Host "Computer Name: $computerName" +Write-Host "IP Addresses: $($ipAddresses -join ', ')" + +# Test local WinRM connectivity +try { + $testSession = New-PSSession -ComputerName localhost -ErrorAction Stop + Remove-PSSession $testSession -ErrorAction SilentlyContinue + StatusCheck "Local WinRM Test" $true +} catch { + StatusCheck "Local WinRM Test" $false "Local connection failed: $($_.Exception.Message)" +} + +Section "Authentication Methods" +try { + $authConfig = winrm get winrm/config/service/auth 2>$null + if ($authConfig) { + Write-Host "Available Authentication Methods:" + Write-Host $authConfig + } +} catch { + Write-Host "Could not retrieve authentication configuration" +} + +Section "Recommended Actions" +Write-Host "If WinRM is not properly configured, run these commands as Administrator:" -ForegroundColor Cyan +Write-Host "" +Write-Host "# Enable PowerShell Remoting and WinRM" -ForegroundColor Yellow +Write-Host "Enable-PSRemoting -Force" +Write-Host "winrm quickconfig -quiet" +Write-Host "" +Write-Host "# Configure basic authentication (for testing)" -ForegroundColor Yellow +Write-Host "winrm set winrm/config/service/auth '@{Basic=`"true`"}'" +Write-Host "winrm set winrm/config/service '@{AllowUnencrypted=`"true`"}'" +Write-Host "" +Write-Host "# Enable firewall rules" -ForegroundColor Yellow +Write-Host "Enable-NetFirewallRule -DisplayGroup 'Windows Remote Management'" +Write-Host "" +Write-Host "# Test from remote machine:" -ForegroundColor Yellow +Write-Host "Test-WSMan -ComputerName $($ipAddresses[0]) -Authentication Basic" + +Section "Current Status Summary" +$winrmRunning = $winrmService -and ($winrmService.Status -eq "Running") +$configExists = try { winrm get winrm/config >$null 2>&1; $true } catch { $false } +$listenersExist = try { winrm enumerate winrm/config/listener >$null 2>&1; $true } catch { $false } +$firewallEnabled = ($winrmHttpRule -ne $null) + +if ($winrmRunning -and $configExists -and $listenersExist -and $firewallEnabled) { + Write-Host "🎉 WinRM appears to be ready for remote connections!" -ForegroundColor Green +} elseif ($winrmRunning -and $configExists) { + Write-Host "⚠️ WinRM is partially configured - may need firewall or listener setup" -ForegroundColor Yellow +} else { + Write-Host "❌ WinRM requires configuration before remote access will work" -ForegroundColor Red +} \ No newline at end of file diff --git a/scripts/powershell/cleanup-and-fix-winget.ps1 b/scripts/powershell/cleanup-and-fix-winget.ps1 new file mode 100644 index 0000000..97328b8 --- /dev/null +++ b/scripts/powershell/cleanup-and-fix-winget.ps1 @@ -0,0 +1,119 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Continue' + +Write-Host "[*] Cleaning up download shortcuts and fixing winget" + +# Remove download shortcuts +$desktopPath = "C:\Users\User\Desktop" +$shortcutsToRemove = @( + "Download-Chrome.url", + "Download-7-Zip.url", + "Download-AnyDesk.url", + "Download-LINE.url", + "Download-Adobe-Reader.url", + "Installation-Instructions.txt" +) + +Write-Host "[1] Removing download shortcuts..." +foreach ($shortcut in $shortcutsToRemove) { + $filePath = Join-Path $desktopPath $shortcut + if (Test-Path $filePath) { + Remove-Item $filePath -Force + Write-Host " Removed: $shortcut" + } +} + +Write-Host "[2] Checking winget status..." + +# Check if winget is available +try { + $wingetVersion = & winget --version 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-Host " [OK] winget is available: $wingetVersion" + } else { + Write-Host " [!] winget command failed" + throw "winget not working" + } +} catch { + Write-Host " [!] winget not found or not working" + + Write-Host "[3] Installing/Updating winget..." + + # Method 1: Try to install App Installer (contains winget) + try { + Write-Host " Attempting to install Microsoft App Installer..." + + # Download and install App Installer + $appInstallerUrl = "https://github.com/microsoft/winget-cli/releases/latest/download/Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle" + $downloadPath = "$env:TEMP\AppInstaller.msixbundle" + + Invoke-WebRequest -Uri $appInstallerUrl -OutFile $downloadPath -UseBasicParsing + + # Install the package + Add-AppxPackage -Path $downloadPath + + Write-Host " [OK] App Installer installed" + + # Clean up + Remove-Item $downloadPath -Force -ErrorAction SilentlyContinue + + } catch { + Write-Host " [!] Failed to install App Installer: $($_.Exception.Message)" + + # Method 2: Try alternative approach + Write-Host " Trying alternative winget installation..." + + try { + # Check Windows version and suggest manual installation + $osVersion = [System.Environment]::OSVersion.Version + Write-Host " Windows Version: $osVersion" + + if ($osVersion.Major -ge 10 -and $osVersion.Build -ge 17763) { + Write-Host " [INFO] Windows version supports winget" + Write-Host " [ACTION] Manual steps required:" + Write-Host " 1. Open Microsoft Store" + Write-Host " 2. Search for 'App Installer'" + Write-Host " 3. Install/Update App Installer" + Write-Host " 4. Restart PowerShell session" + } else { + Write-Host " [!] Windows version may not support winget" + Write-Host " Consider updating Windows or using manual installation" + } + + } catch { + Write-Host " [!] Could not determine Windows version" + } + } +} + +Write-Host "[4] Final winget verification..." + +# Wait a moment and test again +Start-Sleep -Seconds 3 + +try { + $wingetTest = & winget list --source winget 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-Host " [OK] winget is working properly" + Write-Host " [INFO] Ready for application installation" + + # Test a simple winget operation + Write-Host "[5] Testing winget functionality..." + $testResult = & winget search "7zip.7zip" 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-Host " [OK] winget search working" + } else { + Write-Host " [!] winget search failed, may need source configuration" + } + + } else { + Write-Host " [!] winget still not working properly" + Write-Host " Output: $wingetTest" + } +} catch { + Write-Host " [!] winget verification failed: $($_.Exception.Message)" +} + +Write-Host "" +Write-Host "Cleanup and winget setup completed!" +Write-Host "If winget is working, we can proceed with automated installation." \ No newline at end of file diff --git a/scripts/powershell/configure-ime.ps1 b/scripts/powershell/configure-ime.ps1 new file mode 100644 index 0000000..03d467f --- /dev/null +++ b/scripts/powershell/configure-ime.ps1 @@ -0,0 +1,112 @@ +param( + [string]$TargetUser = 'User' +) + +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Stop' + +# Resolve target user SID +$sid = (New-Object System.Security.Principal.NTAccount($TargetUser)).Translate([System.Security.Principal.SecurityIdentifier]).Value +$hivePath = "HKEY_USERS\$sid" +$regBase = "Registry::$hivePath" +$ntuserDat = "C:\Users\$TargetUser\NTUSER.DAT" +$weLoaded = $false + +if (-not (Test-Path $regBase)) { + if (-not (Test-Path $ntuserDat)) { + # Try alternate casing (NTFS is case-insensitive but PS sees the literal case) + $alt = Get-Item "C:\Users\*" | Where-Object { $_.Name -ieq $TargetUser } | Select-Object -First 1 + if ($alt) { $ntuserDat = Join-Path $alt.FullName 'NTUSER.DAT' } + } + if (-not (Test-Path $ntuserDat)) { throw "NTUSER.DAT not found for $TargetUser" } + Write-Host "[*] Loading $ntuserDat" + & reg.exe load $hivePath $ntuserDat | Out-Null + if ($LASTEXITCODE -ne 0) { throw "reg load failed" } + $weLoaded = $true +} + +try { + # ---------- 1. Add en-US to language list as FIRST language (default) ---------- + # This targets the CURRENT user running this script. For the target user, we + # write the preload keys directly into their hive. + Write-Host "[*] Setting target user's keyboard preload (en-US first, zh-TW second)" + + $preloadPath = "$regBase\Keyboard Layout\Preload" + if (-not (Test-Path $preloadPath)) { New-Item -Path $preloadPath -Force | Out-Null } + # 00000409 = English (US); 00000404 = Chinese (Traditional, Taiwan) + New-ItemProperty -Path $preloadPath -Name '1' -Value '00000409' -PropertyType String -Force | Out-Null + New-ItemProperty -Path $preloadPath -Name '2' -Value '00000404' -PropertyType String -Force | Out-Null + + $substitutesPath = "$regBase\Keyboard Layout\Substitutes" + if (Test-Path $substitutesPath) { + Remove-Item $substitutesPath -Recurse -Force + } + + # ---------- 2. Set Chinese IME to default to English (alphanumeric) mode ---------- + Write-Host "[*] Setting Microsoft Bopomofo to default English mode" + $chtPath = "$regBase\Software\Microsoft\InputMethod\Settings\CHT" + if (-not (Test-Path $chtPath)) { New-Item -Path $chtPath -Force | Out-Null } + # 0 = default English mode, 1 = default Chinese mode + New-ItemProperty -Path $chtPath -Name 'Enable Default Input Mode' -Value 0 -PropertyType DWord -Force | Out-Null + + # ---------- 3. Set Bopomofo's Chinese/English switch hotkey to Ctrl+Space ---------- + # Microsoft Bopomofo exposes this option in its Settings UI: + # Shift (default) | Ctrl + Space | Caps Lock + # The registry value is 'Enable Ctrl Space Switch Chinese English' (boolean-ish). + Write-Host "[*] Setting Ctrl+Space as Chinese/English switch for MS Bopomofo" + New-ItemProperty -Path $chtPath -Name 'Enable Ctrl Space Switch Chinese English' -Value 1 -PropertyType DWord -Force | Out-Null + # Also disable Shift as the switch (optional, prevents confusion) + New-ItemProperty -Path $chtPath -Name 'Enable Shift Chinese English' -Value 0 -PropertyType DWord -Force | Out-Null + + # ---------- 4. Disable OS-level Ctrl+Shift / Alt+Shift language toggle ---------- + # So Ctrl+Space is the only thing that triggers language switching. + Write-Host "[*] Disabling OS-level Language/Layout hotkeys" + $togglePath = "$regBase\Keyboard Layout\Toggle" + if (-not (Test-Path $togglePath)) { New-Item -Path $togglePath -Force | Out-Null } + # Values: 1 = Ctrl+Shift, 2 = Left Alt+Shift, 3 = Grave Accent, 4 = Not assigned + New-ItemProperty -Path $togglePath -Name 'Hotkey' -Value '3' -PropertyType String -Force | Out-Null + New-ItemProperty -Path $togglePath -Name 'Language Hotkey' -Value '3' -PropertyType String -Force | Out-Null + New-ItemProperty -Path $togglePath -Name 'Layout Hotkey' -Value '3' -PropertyType String -Force | Out-Null + + # ---------- 5. Tell the language subsystem en-US is the default ---------- + # Remove user-profile language forcing so that Preload order is respected + $userProfileLangPath = "$regBase\Control Panel\International\User Profile" + if (Test-Path $userProfileLangPath) { + $langs = (Get-ItemProperty $userProfileLangPath).Languages + if ($langs) { + # Prepend en-US to the language list + $newLangs = @('en-US') + ($langs | Where-Object { $_ -ne 'en-US' }) + New-ItemProperty -Path $userProfileLangPath -Name 'Languages' -Value $newLangs -PropertyType MultiString -Force | Out-Null + Write-Host "[*] Updated user-profile language list: $($newLangs -join ', ')" + } + } + + # Ensure per-language subkeys exist + $enUSPath = "$userProfileLangPath\en-US" + if (-not (Test-Path $enUSPath)) { New-Item -Path $enUSPath -Force | Out-Null } + # CachedLanguageName is cosmetic but prevents re-detection flakiness + New-ItemProperty -Path $enUSPath -Name '0409:00000409' -Value 1 -PropertyType DWord -Force | Out-Null + + Write-Host "" + Write-Host "[OK] IME configured for '$TargetUser'" + Write-Host "Summary:" + Write-Host " - Default input: English (US) keyboard" + Write-Host " - Microsoft Bopomofo: starts in English (alphanumeric) mode" + Write-Host " - Ctrl+Space: toggles between English / Chinese within Bopomofo" + Write-Host " - OS-level language hotkeys (Ctrl+Shift / Alt+Shift): disabled" +} +finally { + if ($weLoaded) { + [GC]::Collect(); [GC]::WaitForPendingFinalizers() + [GC]::Collect(); [GC]::WaitForPendingFinalizers() + Start-Sleep -Milliseconds 500 + & reg.exe unload $hivePath 2>&1 | Out-Null + if ($LASTEXITCODE -eq 0) { Write-Host "[*] Unloaded $hivePath" } + else { Write-Host "[!] reg unload returned $LASTEXITCODE" } + } +} + +Write-Host "" +Write-Host "Note: Settings apply on '$TargetUser' next login. If Bopomofo doesn't pick up" +Write-Host "Ctrl+Space automatically, open 'Microsoft Bopomofo settings' -> 'Keys' and" +Write-Host "confirm 'Chinese/English switch' = Ctrl+Space (this registry write should do it)." diff --git a/scripts/powershell/create-download-links.ps1 b/scripts/powershell/create-download-links.ps1 new file mode 100644 index 0000000..bac1285 --- /dev/null +++ b/scripts/powershell/create-download-links.ps1 @@ -0,0 +1,94 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 + +Write-Host "[*] Creating download shortcuts for essential applications" + +$desktopPath = "C:\Users\User\Desktop" + +# Application download links +$apps = @( + @{ + Name = "Google Chrome" + Url = "https://dl.google.com/chrome/install/GoogleChromeStandaloneEnterprise64.msi" + FileName = "Download-Chrome.url" + }, + @{ + Name = "7-Zip" + Url = "https://www.7-zip.org/download.html" + FileName = "Download-7-Zip.url" + }, + @{ + Name = "AnyDesk" + Url = "https://anydesk.com/download" + FileName = "Download-AnyDesk.url" + }, + @{ + Name = "LINE" + Url = "https://line.me/download" + FileName = "Download-LINE.url" + }, + @{ + Name = "Adobe Reader" + Url = "https://get.adobe.com/reader/" + FileName = "Download-Adobe-Reader.url" + } +) + +Write-Host "Creating download shortcuts on User desktop..." + +foreach ($app in $apps) { + $shortcutContent = @" +[InternetShortcut] +URL=$($app.Url) +IconIndex=0 +"@ + + $filePath = Join-Path $desktopPath $app.FileName + $shortcutContent | Out-File $filePath -Encoding ASCII + Write-Host " Created: $($app.FileName)" +} + +# Create installation instructions +$instructions = @" +=== Application Installation Instructions === + +1. Google Chrome: + - Double-click Download-Chrome.url + - Download the .msi file + - Run the installer (silent install) + +2. 7-Zip: + - Double-click Download-7-Zip.url + - Download the x64 .msi version + - Run installer + +3. AnyDesk: + - Double-click Download-AnyDesk.url + - Download AnyDesk + - Run installer + +4. LINE: + - Double-click Download-LINE.url + - Download LINE for Windows + - Run installer + +5. Adobe Reader: + - Double-click Download-Adobe-Reader.url + - Download Adobe Acrobat Reader + - Run installer + +All applications will be installed for all users. +"@ + +$instructionsPath = Join-Path $desktopPath "Installation-Instructions.txt" +$instructions | Out-File $instructionsPath -Encoding UTF8 + +Write-Host "" +Write-Host "[OK] Download shortcuts created on User desktop:" +Write-Host " - Download-Chrome.url" +Write-Host " - Download-7-Zip.url" +Write-Host " - Download-AnyDesk.url" +Write-Host " - Download-LINE.url" +Write-Host " - Download-Adobe-Reader.url" +Write-Host " - Installation-Instructions.txt" +Write-Host "" +Write-Host "User can now easily download and install applications manually." \ No newline at end of file diff --git a/scripts/powershell/debug-winrm-network.ps1 b/scripts/powershell/debug-winrm-network.ps1 new file mode 100644 index 0000000..e2433c3 --- /dev/null +++ b/scripts/powershell/debug-winrm-network.ps1 @@ -0,0 +1,86 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'SilentlyContinue' + +function Section($title) { + Write-Host '' + Write-Host ('=' * 8) $title ('=' * 8) -ForegroundColor Green +} + +Section "WinRM Network Diagnostics" + +# Check if WinRM is running +$winrmService = Get-Service WinRM +Write-Host "WinRM Service Status: $($winrmService.Status)" -ForegroundColor $(if($winrmService.Status -eq 'Running') {'Green'} else {'Red'}) + +# Check WinRM listeners +Section "WinRM Listeners" +try { + $listeners = winrm enumerate winrm/config/listener + Write-Host $listeners +} catch { + Write-Host "Failed to enumerate listeners: $_" -ForegroundColor Red +} + +# Check firewall rules specifically for WinRM +Section "Firewall Rules for WinRM" +$winrmRules = Get-NetFirewallRule | Where-Object { $_.DisplayName -like "*WinRM*" -or $_.DisplayName -like "*Windows Remote Management*" } +foreach ($rule in $winrmRules) { + $ruleDetails = Get-NetFirewallPortFilter -AssociatedNetFirewallRule $rule -ErrorAction SilentlyContinue + $color = if ($rule.Enabled) { 'Green' } else { 'Red' } + Write-Host "$($rule.DisplayName): $($rule.Enabled) [$($rule.Direction)] Port: $($ruleDetails.LocalPort)" -ForegroundColor $color +} + +# Check network connections on port 5985 +Section "Network Connections on Port 5985" +$connections = Get-NetTCPConnection -LocalPort 5985 -ErrorAction SilentlyContinue +if ($connections) { + $connections | Select-Object LocalAddress, LocalPort, State, OwningProcess | Format-Table -AutoSize +} else { + Write-Host "No connections found on port 5985" -ForegroundColor Red +} + +# Check listening ports +Section "Listening Ports (including 5985)" +Get-NetTCPConnection -State Listen | Where-Object { $_.LocalPort -in @(22, 5985, 5986) } | + Select-Object LocalAddress, LocalPort, State, OwningProcess | Format-Table -AutoSize + +# Get detailed WinRM configuration +Section "Detailed WinRM Configuration" +try { + Write-Host "Service Configuration:" -ForegroundColor Yellow + winrm get winrm/config/service + + Write-Host "`nListener Configuration:" -ForegroundColor Yellow + winrm get winrm/config/listener?Address=*+Transport=HTTP + +} catch { + Write-Host "Failed to get WinRM config: $_" -ForegroundColor Red +} + +# Test local WinRM connection +Section "Local WinRM Connection Test" +try { + $testResult = Test-WSMan -ComputerName localhost -ErrorAction Stop + Write-Host "✅ Local WinRM test successful" -ForegroundColor Green +} catch { + Write-Host "❌ Local WinRM test failed: $_" -ForegroundColor Red +} + +# Network interface information +Section "Network Interfaces" +Get-NetIPAddress -AddressFamily IPv4 | Where-Object { $_.InterfaceAlias -notmatch 'Loopback' } | + Select-Object InterfaceAlias, IPAddress, PrefixLength | Format-Table -AutoSize + +# Proposed fixes +Section "Recommended Fixes" +Write-Host "1. Enable WinRM firewall rules:" -ForegroundColor Yellow +Write-Host " Enable-NetFirewallRule -DisplayName 'Windows Remote Management (HTTP-In)'" -ForegroundColor White + +Write-Host "`n2. Set AllowUnencrypted to true:" -ForegroundColor Yellow +Write-Host " winrm set winrm/config/service '@{AllowUnencrypted=`"true`"}'" -ForegroundColor White + +Write-Host "`n3. Ensure listener is on all interfaces:" -ForegroundColor Yellow +Write-Host " winrm set winrm/config/listener?Address=*+Transport=HTTP '@{Enabled=`"true`"}'" -ForegroundColor White + +Write-Host "`n4. Test from remote after fixes:" -ForegroundColor Yellow +Write-Host " Test-WSMan -ComputerName 192.168.88.108 -UseSSL:`$false" -ForegroundColor White \ No newline at end of file diff --git a/scripts/powershell/diagnose-winget.ps1 b/scripts/powershell/diagnose-winget.ps1 new file mode 100644 index 0000000..32482d4 --- /dev/null +++ b/scripts/powershell/diagnose-winget.ps1 @@ -0,0 +1,83 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Continue' + +Write-Host "[*] Diagnosing winget configuration and connectivity" +Write-Host "" + +Write-Host "[1] Checking winget version and status..." +try { + $wingetVersion = & winget --version + Write-Host " winget version: $wingetVersion" +} catch { + Write-Host " [!] winget command failed" +} + +Write-Host "" +Write-Host "[2] Checking winget sources..." +try { + $sources = & winget source list + Write-Host " winget sources:" + Write-Host "$sources" +} catch { + Write-Host " [!] Failed to list winget sources" +} + +Write-Host "" +Write-Host "[3] Testing network connectivity..." + +$testUrls = @( + "github.com", + "cdn.winget.microsoft.com", + "storeedgefd.dsx.mp.microsoft.com" +) + +foreach ($url in $testUrls) { + try { + $testResult = Test-NetConnection -ComputerName $url -Port 443 -WarningAction SilentlyContinue + if ($testResult.TcpTestSucceeded) { + Write-Host " [OK] $url - Accessible" + } else { + Write-Host " [!] $url - Not accessible" + } + } catch { + Write-Host " [!] $url - Test failed" + } +} + +Write-Host "" +Write-Host "[4] Testing simple winget search..." +try { + Write-Host " Searching for Chrome..." + $searchResult = & winget search "Google.Chrome" --exact + Write-Host " Search result: $searchResult" +} catch { + Write-Host " [!] winget search failed" +} + +Write-Host "" +Write-Host "[5] Checking Windows version compatibility..." +$windowsVersion = Get-ComputerInfo | Select-Object WindowsProductName, WindowsVersion, WindowsBuildLabEx +Write-Host " OS: $($windowsVersion.WindowsProductName)" +Write-Host " Version: $($windowsVersion.WindowsVersion)" +Write-Host " Build: $($windowsVersion.WindowsBuildLabEx)" + +Write-Host "" +Write-Host "[6] Testing manual installation with verbose output..." +Write-Host " Attempting to install 7-Zip with verbose logging..." + +try { + $installTest = & winget install 7zip.7zip --accept-package-agreements --accept-source-agreements --verbose 2>&1 + Write-Host " Install output:" + Write-Host "$installTest" +} catch { + Write-Host " [!] Manual install test failed: $($_.Exception.Message)" +} + +Write-Host "" +Write-Host "Diagnosis completed!" +Write-Host "" +Write-Host "Possible solutions based on findings:" +Write-Host "1. If network connectivity issues: Configure proxy or firewall" +Write-Host "2. If source issues: Reset winget sources" +Write-Host "3. If permissions issues: Run as Administrator" +Write-Host "4. If Windows version issues: Update Windows or use manual installation" \ No newline at end of file diff --git a/scripts/powershell/enable-winrm.ps1 b/scripts/powershell/enable-winrm.ps1 new file mode 100644 index 0000000..5bb269d --- /dev/null +++ b/scripts/powershell/enable-winrm.ps1 @@ -0,0 +1,155 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Stop' + +function Section($title) { + Write-Host '' + Write-Host ('=' * 8) $title ('=' * 8) -ForegroundColor Green +} + +function StatusUpdate($message) { + Write-Host "✅ $message" -ForegroundColor Green +} + +function WarningUpdate($message) { + Write-Host "⚠️ $message" -ForegroundColor Yellow +} + +Write-Host "Starting WinRM Configuration..." -ForegroundColor Cyan +Write-Host "This script will configure WinRM for remote management." -ForegroundColor Cyan +Write-Host "" + +# Check if running as Administrator +$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) +$isAdmin = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) + +if (-not $isAdmin) { + Write-Host "❌ ERROR: This script must be run as Administrator" -ForegroundColor Red + Write-Host "Please run PowerShell as Administrator and try again." -ForegroundColor Red + exit 1 +} + +StatusUpdate "Running as Administrator" + +Section "Enable PowerShell Remoting" +try { + Enable-PSRemoting -Force -SkipNetworkProfileCheck | Out-Null + StatusUpdate "PowerShell Remoting enabled" +} catch { + WarningUpdate "PowerShell Remoting may already be enabled: $($_.Exception.Message)" +} + +Section "Configure WinRM Service" +try { + # Ensure WinRM service is set to automatic startup + Set-Service -Name WinRM -StartupType Automatic + Start-Service -Name WinRM + StatusUpdate "WinRM service configured and started" +} catch { + WarningUpdate "WinRM service configuration: $($_.Exception.Message)" +} + +Section "Run WinRM Quick Configuration" +try { + $quickConfigOutput = cmd /c "winrm quickconfig -quiet" 2>&1 + StatusUpdate "WinRM quick configuration completed" +} catch { + WarningUpdate "WinRM quickconfig: $($_.Exception.Message)" +} + +Section "Configure Authentication Methods" +try { + # Enable Basic authentication for simplicity (can be disabled later for production) + cmd /c 'winrm set winrm/config/service/auth @{Basic="true"}' | Out-Null + StatusUpdate "Basic authentication enabled" + + # Allow unencrypted traffic for HTTP (can be disabled in production) + cmd /c 'winrm set winrm/config/service @{AllowUnencrypted="true"}' | Out-Null + StatusUpdate "Unencrypted communication allowed (for HTTP testing)" + +} catch { + WarningUpdate "Authentication configuration: $($_.Exception.Message)" +} + +Section "Configure Firewall Rules" +try { + # Enable Windows Remote Management firewall rules + Enable-NetFirewallRule -DisplayGroup "Windows Remote Management" -ErrorAction SilentlyContinue + StatusUpdate "Windows Remote Management firewall rules enabled" +} catch { + WarningUpdate "Firewall configuration: $($_.Exception.Message)" +} + +Section "Create HTTP Listener (if needed)" +try { + # Check if HTTP listener exists + $httpListener = winrm enumerate winrm/config/listener | Select-String "Transport = HTTP" + if (-not $httpListener) { + winrm create winrm/config/listener?Address=*+Transport=HTTP | Out-Null + StatusUpdate "HTTP listener created on port 5985" + } else { + StatusUpdate "HTTP listener already exists" + } +} catch { + WarningUpdate "HTTP listener configuration: $($_.Exception.Message)" +} + +Section "Test Local Connection" +try { + $testSession = New-PSSession -ComputerName localhost -ErrorAction Stop + Remove-PSSession $testSession -ErrorAction SilentlyContinue + StatusUpdate "Local WinRM connection test successful" +} catch { + WarningUpdate "Local connection test failed: $($_.Exception.Message)" + Write-Host " This may be normal if additional configuration is needed" +} + +Section "Configuration Summary" +Write-Host "" +Write-Host "WinRM has been configured with the following settings:" -ForegroundColor Cyan +Write-Host "" + +# Display current configuration +try { + Write-Host "Service Status:" -ForegroundColor Yellow + Get-Service WinRM | Select-Object Name, Status, StartType | Format-List + + Write-Host "Listeners:" -ForegroundColor Yellow + winrm enumerate winrm/config/listener + + Write-Host "Authentication Methods:" -ForegroundColor Yellow + winrm get winrm/config/service/auth + + Write-Host "Firewall Rules:" -ForegroundColor Yellow + Get-NetFirewallRule -DisplayGroup "*Remote Management*" | Where-Object Enabled -eq $true | + Select-Object DisplayName, Direction, Action | Format-Table -AutoSize + +} catch { + Write-Host "Could not retrieve full configuration details" +} + +Section "Connection Information" +$ipAddresses = Get-NetIPAddress -AddressFamily IPv4 | Where-Object { $_.InterfaceAlias -notmatch 'Loopback' } | Select-Object -ExpandProperty IPAddress +$computerName = $env:COMPUTERNAME + +Write-Host "Computer Name: $computerName" -ForegroundColor Cyan +Write-Host "IP Addresses: $($ipAddresses -join ', ')" -ForegroundColor Cyan +Write-Host "" +Write-Host "To test from a remote machine, use:" -ForegroundColor Yellow +Write-Host "Test-WSMan -ComputerName $($ipAddresses[0])" -ForegroundColor White +Write-Host " or" +Write-Host "Test-WSMan -ComputerName $computerName" -ForegroundColor White +Write-Host "" +Write-Host "For PowerShell remoting:" -ForegroundColor Yellow +Write-Host "`$session = New-PSSession -ComputerName $($ipAddresses[0]) -Credential (Get-Credential)" -ForegroundColor White + +Section "Security Notes" +Write-Host "⚠️ SECURITY CONSIDERATIONS:" -ForegroundColor Yellow +Write-Host "" +Write-Host "1. Basic authentication and unencrypted communication are enabled for testing" -ForegroundColor Yellow +Write-Host "2. For production use, consider:" -ForegroundColor Yellow +Write-Host " - Disabling Basic auth: winrm set winrm/config/service/auth @{Basic=`"false`"}" -ForegroundColor White +Write-Host " - Disabling unencrypted: winrm set winrm/config/service @{AllowUnencrypted=`"false`"}" -ForegroundColor White +Write-Host " - Setting up HTTPS listener with proper certificates" -ForegroundColor White +Write-Host " - Using Kerberos authentication in domain environments" -ForegroundColor White +Write-Host "" +Write-Host "🎉 WinRM configuration completed!" -ForegroundColor Green \ No newline at end of file diff --git a/scripts/powershell/firewall-allow-ssh.ps1 b/scripts/powershell/firewall-allow-ssh.ps1 new file mode 100644 index 0000000..1e6a4ed --- /dev/null +++ b/scripts/powershell/firewall-allow-ssh.ps1 @@ -0,0 +1,36 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Stop' + +$ruleName = 'SSH TCP 22 (Allow Inbound)' + +# Idempotent: remove any existing rule with the same display name, then recreate. +Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue | Remove-NetFirewallRule + +New-NetFirewallRule ` + -DisplayName $ruleName ` + -Description 'Allow inbound SSH on TCP 22 for all firewall profiles (Domain, Private, Public)' ` + -Direction Inbound ` + -Protocol TCP ` + -LocalPort 22 ` + -Action Allow ` + -Profile Any ` + -Enabled True | Out-Null + +Write-Host "[*] Re-enabling firewall profiles (Domain, Private, Public)" +Set-NetFirewallProfile -Profile Domain,Private,Public -Enabled True + +Write-Host "" +Write-Host "=== Rule ===" +Get-NetFirewallRule -DisplayName $ruleName | + Select-Object DisplayName,Enabled,Direction,Action,Profile | + Format-List + +Write-Host "=== Port filter ===" +Get-NetFirewallRule -DisplayName $ruleName | Get-NetFirewallPortFilter | + Format-List Protocol,LocalPort + +Write-Host "=== Profile state ===" +Get-NetFirewallProfile | Select-Object Name,Enabled,DefaultInboundAction,DefaultOutboundAction | + Format-Table -AutoSize + +Write-Host "[OK] SSH 22 is now permanently allowed across all profiles." diff --git a/scripts/powershell/fix-winrm.ps1 b/scripts/powershell/fix-winrm.ps1 new file mode 100644 index 0000000..63782a2 --- /dev/null +++ b/scripts/powershell/fix-winrm.ps1 @@ -0,0 +1,62 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Stop' + +function Section($title) { + Write-Host '' + Write-Host ('=' * 8) $title ('=' * 8) -ForegroundColor Green +} + +function StatusUpdate($message) { + Write-Host "✅ $message" -ForegroundColor Green +} + +function WarningUpdate($message) { + Write-Host "⚠️ $message" -ForegroundColor Yellow +} + +Write-Host "Fixing remaining WinRM configuration issues..." -ForegroundColor Cyan + +Section "Fix Allow Unencrypted Setting" +try { + cmd /c 'winrm set winrm/config/service @{AllowUnencrypted="true"}' | Out-Null + StatusUpdate "Allow unencrypted communication enabled" +} catch { + WarningUpdate "Failed to set AllowUnencrypted: $($_.Exception.Message)" +} + +Section "Enable WinRM Firewall Rules" +try { + Enable-NetFirewallRule -DisplayGroup "Windows Remote Management" -ErrorAction SilentlyContinue + StatusUpdate "WinRM firewall rules enabled" + + # Show enabled WinRM rules + $winrmRules = Get-NetFirewallRule -DisplayGroup "*Remote Management*" | Where-Object Enabled -eq $true + Write-Host "Enabled WinRM firewall rules:" -ForegroundColor Yellow + $winrmRules | Select-Object DisplayName, Direction, Action | Format-Table -AutoSize + +} catch { + WarningUpdate "Firewall rule configuration: $($_.Exception.Message)" +} + +Section "Verify Settings" +try { + Write-Host "Current WinRM Service Configuration:" -ForegroundColor Yellow + winrm get winrm/config/service + + Write-Host "`nCurrent Authentication Settings:" -ForegroundColor Yellow + winrm get winrm/config/service/auth + +} catch { + WarningUpdate "Could not retrieve configuration: $($_.Exception.Message)" +} + +Section "Final Connection Test" +try { + $testSession = New-PSSession -ComputerName localhost -ErrorAction Stop + Remove-PSSession $testSession -ErrorAction SilentlyContinue + StatusUpdate "Final local WinRM test successful" +} catch { + WarningUpdate "Final test failed: $($_.Exception.Message)" +} + +Write-Host "`n🎉 WinRM configuration fixes completed!" -ForegroundColor Green \ No newline at end of file diff --git a/scripts/powershell/install-apps-direct.ps1 b/scripts/powershell/install-apps-direct.ps1 new file mode 100644 index 0000000..6e9bb09 --- /dev/null +++ b/scripts/powershell/install-apps-direct.ps1 @@ -0,0 +1,122 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Continue' + +Write-Host "[*] Installing applications via direct download" +Write-Host "" + +$downloadFolder = "$env:TEMP\AppDownloads" +New-Item -ItemType Directory -Force -Path $downloadFolder | Out-Null + +function Install-App { + param( + [string]$Name, + [string]$Url, + [string]$FileName, + [string]$InstallArgs + ) + + Write-Host "Installing $Name..." + Write-Host " URL: $Url" + + $downloadPath = Join-Path $downloadFolder $FileName + + try { + Write-Host " Downloading..." + Invoke-WebRequest -Uri $Url -OutFile $downloadPath -UseBasicParsing + + Write-Host " Installing..." + if ($FileName.EndsWith(".msi")) { + & msiexec /i $downloadPath $InstallArgs + } else { + & $downloadPath $InstallArgs + } + + Write-Host " [OK] $Name installation completed" + return $true + + } catch { + Write-Host " [ERROR] Failed to install $Name" + Write-Host " Error: $($_.Exception.Message)" + return $false + } +} + +# Application definitions +$apps = @( + @{ + Name = "Google Chrome" + Url = "https://dl.google.com/chrome/install/GoogleChromeStandaloneEnterprise64.msi" + FileName = "ChromeSetup.msi" + InstallArgs = "/quiet /norestart" + }, + @{ + Name = "7-Zip" + Url = "https://www.7-zip.org/a/7z2301-x64.msi" + FileName = "7zip-setup.msi" + InstallArgs = "/quiet /norestart" + }, + @{ + Name = "AnyDesk" + Url = "https://download.anydesk.com/AnyDesk.msi" + FileName = "AnyDesk.msi" + InstallArgs = "/quiet /norestart" + } +) + +$successCount = 0 + +foreach ($app in $apps) { + if (Install-App -Name $app.Name -Url $app.Url -FileName $app.FileName -InstallArgs $app.InstallArgs) { + $successCount++ + } + Write-Host "" + Start-Sleep -Seconds 2 +} + +# Manual instructions for apps that need special handling +Write-Host "=== Manual Downloads Required ===" +Write-Host "" + +Write-Host "LINE:" +Write-Host " Please download from: https://line.me/download" +Write-Host " Or direct: https://desktop.line-scdn.net/win/new/LineInst.exe" +Write-Host "" + +Write-Host "Adobe Reader:" +Write-Host " Please download from: https://get.adobe.com/reader/" +Write-Host " Select version for Windows and download" +Write-Host "" + +Write-Host "=== Installation Summary ===" +Write-Host "Automatically installed: $successCount / 3 applications" +Write-Host "Manual download required: 2 applications (LINE, Adobe Reader)" +Write-Host "" + +# Create desktop shortcuts for manual downloads +$desktopPath = "C:\Users\User\Desktop" + +# Create LINE download shortcut +$lineShortcut = @" +[InternetShortcut] +URL=https://line.me/download +IconIndex=0 +"@ +$lineShortcut | Out-File "$desktopPath\Download-LINE.url" -Encoding ASCII + +# Create Adobe Reader download shortcut +$adobeShortcut = @" +[InternetShortcut] +URL=https://get.adobe.com/reader/ +IconIndex=0 +"@ +$adobeShortcut | Out-File "$desktopPath\Download-Adobe-Reader.url" -Encoding ASCII + +Write-Host "Created download shortcuts on User desktop:" +Write-Host " - Download-LINE.url" +Write-Host " - Download-Adobe-Reader.url" + +# Clean up +Remove-Item $downloadFolder -Recurse -Force -ErrorAction SilentlyContinue + +Write-Host "" +Write-Host "Installation process completed!" \ No newline at end of file diff --git a/scripts/powershell/install-apps-fixed.ps1 b/scripts/powershell/install-apps-fixed.ps1 new file mode 100644 index 0000000..7073c5f --- /dev/null +++ b/scripts/powershell/install-apps-fixed.ps1 @@ -0,0 +1,103 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Continue' + +Write-Host "[*] Installing applications using winget (fixed sources)" +Write-Host "" + +# Applications to install with explicit winget source +$apps = @( + @{Name="Google Chrome"; Id="Google.Chrome"}, + @{Name="7-Zip"; Id="7zip.7zip"}, + @{Name="AnyDesk"; Id="AnyDeskSoftwareGmbH.AnyDesk"}, + @{Name="LINE"; Id="LINE.LINE"}, + @{Name="Adobe Reader"; Id="Adobe.Acrobat.Reader.64-bit"} +) + +$installedCount = 0 +$totalApps = $apps.Count + +Write-Host "Installing $totalApps applications using winget source..." +Write-Host "" + +foreach ($app in $apps) { + Write-Host "[$($installedCount + 1)/$totalApps] Installing $($app.Name)..." + + try { + # Install using explicit winget source to avoid msstore certificate issues + Write-Host " Downloading and installing from winget source..." + $installResult = & winget install $app.Id --source winget --accept-package-agreements --accept-source-agreements --silent + + if ($LASTEXITCODE -eq 0) { + Write-Host " [OK] $($app.Name) installed successfully" + $installedCount++ + } else { + Write-Host " [!] Installation exit code: $LASTEXITCODE" + + # Try to verify if it was actually installed despite exit code + Start-Sleep -Seconds 3 + $verifyResult = & winget list $app.Id --source winget 2>$null + if ($LASTEXITCODE -eq 0) { + Write-Host " [OK] $($app.Name) appears to be installed" + $installedCount++ + } else { + Write-Host " [ERROR] $($app.Name) installation failed" + + # Try alternative approach for some apps + if ($app.Name -eq "Google Chrome") { + Write-Host " Trying Chrome enterprise installer..." + try { + $chromeUrl = "https://dl.google.com/chrome/install/GoogleChromeStandaloneEnterprise64.msi" + $chromePath = "$env:TEMP\ChromeSetup.msi" + Invoke-WebRequest -Uri $chromeUrl -OutFile $chromePath -UseBasicParsing + & msiexec /i $chromePath /quiet /norestart + Start-Sleep -Seconds 5 + Remove-Item $chromePath -Force -ErrorAction SilentlyContinue + Write-Host " [OK] Chrome installed via direct MSI" + $installedCount++ + } catch { + Write-Host " [!] Chrome direct install failed" + } + } + } + } + + } catch { + Write-Host " [ERROR] Exception: $($_.Exception.Message)" + } + + Write-Host "" + Start-Sleep -Seconds 2 +} + +Write-Host "=== Installation Summary ===" +Write-Host "Successfully installed: $installedCount / $totalApps applications" +Write-Host "" + +# Final verification +Write-Host "Final verification:" + +$verificationChecks = @( + @{Name="Chrome"; Paths=@("C:\Program Files\Google\Chrome\Application\chrome.exe", "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe")}, + @{Name="7-Zip"; Paths=@("C:\Program Files\7-Zip\7z.exe")}, + @{Name="AnyDesk"; Paths=@("C:\Program Files (x86)\AnyDesk\AnyDesk.exe", "C:\Program Files\AnyDesk\AnyDesk.exe")}, + @{Name="Adobe Reader"; Paths=@("C:\Program Files (x86)\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe", "C:\Program Files\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe")}, + @{Name="LINE"; Paths=@("C:\Users\$env:USERNAME\AppData\Local\LINE\bin\LineLauncher.exe", "C:\Program Files\LINE\bin\LineLauncher.exe")} +) + +foreach ($check in $verificationChecks) { + $found = $false + foreach ($path in $check.Paths) { + if (Test-Path $path) { + Write-Host " [OK] $($check.Name) - Found at $path" + $found = $true + break + } + } + if (-not $found) { + Write-Host " [!] $($check.Name) - Not found" + } +} + +Write-Host "" +Write-Host "Installation process completed!" +Write-Host "Note: Some applications may require restart to appear in Start Menu" \ No newline at end of file diff --git a/scripts/powershell/install-apps-winget.ps1 b/scripts/powershell/install-apps-winget.ps1 new file mode 100644 index 0000000..26de738 --- /dev/null +++ b/scripts/powershell/install-apps-winget.ps1 @@ -0,0 +1,99 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Continue' + +Write-Host "[*] Installing applications using winget" +Write-Host "" + +# Applications to install +$apps = @( + @{Name="Google Chrome"; Id="Google.Chrome"}, + @{Name="7-Zip"; Id="7zip.7zip"}, + @{Name="AnyDesk"; Id="AnyDeskSoftwareGmbH.AnyDesk"}, + @{Name="LINE"; Id="LINE.LINE"}, + @{Name="Adobe Reader"; Id="Adobe.Acrobat.Reader.64-bit"} +) + +$installedCount = 0 +$totalApps = $apps.Count + +Write-Host "Installing $totalApps applications..." +Write-Host "" + +foreach ($app in $apps) { + Write-Host "[$($installedCount + 1)/$totalApps] Installing $($app.Name)..." + + try { + # First check if already installed + $checkResult = & winget list $app.Id 2>$null + if ($LASTEXITCODE -eq 0) { + Write-Host " [SKIP] $($app.Name) already installed" + $installedCount++ + continue + } + + # Install the application + Write-Host " Downloading and installing..." + $installResult = & winget install $app.Id --accept-package-agreements --accept-source-agreements --silent + + if ($LASTEXITCODE -eq 0) { + Write-Host " [OK] $($app.Name) installed successfully" + $installedCount++ + } else { + Write-Host " [!] $($app.Name) installation may have failed" + Write-Host " Checking if it was actually installed..." + + # Double-check installation + Start-Sleep -Seconds 2 + $verifyResult = & winget list $app.Id 2>$null + if ($LASTEXITCODE -eq 0) { + Write-Host " [OK] $($app.Name) verified as installed" + $installedCount++ + } else { + Write-Host " [ERROR] $($app.Name) installation failed" + } + } + + } catch { + Write-Host " [ERROR] Exception installing $($app.Name): $($_.Exception.Message)" + } + + Write-Host "" + Start-Sleep -Seconds 1 +} + +Write-Host "=== Installation Summary ===" +Write-Host "Successfully processed: $installedCount / $totalApps applications" +Write-Host "" + +# Verification +Write-Host "Verification check:" + +$verificationPaths = @( + @{Name="Chrome"; Path="C:\Program Files\Google\Chrome\Application\chrome.exe"}, + @{Name="7-Zip"; Path="C:\Program Files\7-Zip\7z.exe"}, + @{Name="AnyDesk"; Path="C:\Program Files (x86)\AnyDesk\AnyDesk.exe"}, + @{Name="Adobe Reader"; Path="C:\Program Files (x86)\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe"} +) + +foreach ($check in $verificationPaths) { + if (Test-Path $check.Path) { + Write-Host " [OK] $($check.Name) - Found" + } else { + Write-Host " [?] $($check.Name) - Not found at expected location" + } +} + +# LINE has a different installation path (per-user) +$lineUserPath = "C:\Users\$env:USERNAME\AppData\Local\LINE\bin\LineLauncher.exe" +$lineSystemPath = "C:\Program Files\LINE\bin\LineLauncher.exe" + +if ((Test-Path $lineUserPath) -or (Test-Path $lineSystemPath)) { + Write-Host " [OK] LINE - Found" +} else { + Write-Host " [?] LINE - Not found" +} + +Write-Host "" +Write-Host "Installation completed!" +Write-Host "Applications may take a few moments to appear in Start Menu." +Write-Host "Some applications may require a system restart to function properly." \ No newline at end of file diff --git a/scripts/powershell/install-apps.ps1 b/scripts/powershell/install-apps.ps1 new file mode 100644 index 0000000..2329158 --- /dev/null +++ b/scripts/powershell/install-apps.ps1 @@ -0,0 +1,163 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Continue' + +Write-Host "[*] Installing essential applications for User" +Write-Host "Applications: AnyDesk, LINE, Chrome, 7zip, Adobe Reader" +Write-Host "" + +# Function to test if an app is already installed +function Test-AppInstalled { + param([string]$AppName, [string]$ProcessName) + + # Check via Get-WmiObject for installed programs + $installed = Get-WmiObject -Class Win32_Product | Where-Object { $_.Name -like "*$AppName*" } + if ($installed) { + Write-Host " $AppName is already installed: $($installed.Name)" + return $true + } + + # Check via registry (more reliable for some apps) + $regPaths = @( + "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*", + "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" + ) + + foreach ($path in $regPaths) { + $apps = Get-ItemProperty $path -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -like "*$AppName*" } + if ($apps) { + Write-Host " $AppName is already installed: $($apps[0].DisplayName)" + return $true + } + } + + return $false +} + +# Function to install via winget +function Install-ViaWinget { + param([string]$AppId, [string]$AppName) + + Write-Host " Trying winget install $AppId" + try { + $result = & winget install $AppId --accept-package-agreements --accept-source-agreements --silent + if ($LASTEXITCODE -eq 0) { + Write-Host " [OK] $AppName installed via winget" + return $true + } else { + Write-Host " Winget install failed for $AppName" + return $false + } + } catch { + Write-Host " Winget not available or failed: $($_.Exception.Message)" + return $false + } +} + +# Function to download and install manually +function Install-Manual { + param([string]$Url, [string]$FileName, [string]$Args, [string]$AppName) + + Write-Host " Downloading $AppName from $Url" + + $downloadPath = Join-Path $env:TEMP $FileName + try { + Invoke-WebRequest -Uri $Url -OutFile $downloadPath -UseBasicParsing + Write-Host " Downloaded to $downloadPath" + + Write-Host " Installing $AppName..." + if ($Args) { + & $downloadPath $Args.Split(' ') + } else { + & $downloadPath + } + + Start-Sleep -Seconds 3 + + # Clean up + Remove-Item $downloadPath -Force -ErrorAction SilentlyContinue + Write-Host " [OK] $AppName installation completed" + return $true + + } catch { + Write-Host " [!] Manual install failed for $AppName: $($_.Exception.Message)" + return $false + } +} + +Write-Host "[1/5] Installing AnyDesk" +if (-not (Test-AppInstalled -AppName "AnyDesk" -ProcessName "AnyDesk")) { + if (-not (Install-ViaWinget -AppId "AnyDeskSoftwareGmbH.AnyDesk" -AppName "AnyDesk")) { + Install-Manual -Url "https://download.anydesk.com/AnyDesk.exe" -FileName "AnyDesk.exe" -Args "--install --start-with-win --silent" -AppName "AnyDesk" + } +} else { + Write-Host " [SKIP] AnyDesk already installed" +} + +Write-Host "" +Write-Host "[2/5] Installing LINE" +if (-not (Test-AppInstalled -AppName "LINE" -ProcessName "LINE")) { + if (-not (Install-ViaWinget -AppId "LINE.LINE" -AppName "LINE")) { + Install-Manual -Url "https://desktop.line-scdn.net/win/new/LineInst.exe" -FileName "LineInst.exe" -Args "/S" -AppName "LINE" + } +} else { + Write-Host " [SKIP] LINE already installed" +} + +Write-Host "" +Write-Host "[3/5] Installing Google Chrome" +if (-not (Test-AppInstalled -AppName "Chrome" -ProcessName "chrome")) { + if (-not (Install-ViaWinget -AppId "Google.Chrome" -AppName "Chrome")) { + Install-Manual -Url "https://dl.google.com/chrome/install/GoogleChromeStandaloneEnterprise64.msi" -FileName "ChromeSetup.msi" -Args "/quiet /norestart" -AppName "Chrome" + } +} else { + Write-Host " [SKIP] Chrome already installed" +} + +Write-Host "" +Write-Host "[4/5] Installing 7-Zip" +if (-not (Test-AppInstalled -AppName "7-Zip" -ProcessName "7zFM")) { + if (-not (Install-ViaWinget -AppId "7zip.7zip" -AppName "7-Zip")) { + Install-Manual -Url "https://7-zip.org/a/7z2301-x64.msi" -FileName "7zip-setup.msi" -Args "/quiet /norestart" -AppName "7-Zip" + } +} else { + Write-Host " [SKIP] 7-Zip already installed" +} + +Write-Host "" +Write-Host "[5/5] Installing Adobe Reader" +if (-not (Test-AppInstalled -AppName "Adobe" -ProcessName "AcroRd32")) { + if (-not (Install-ViaWinget -AppId "Adobe.Acrobat.Reader.64-bit" -AppName "Adobe Reader")) { + # Adobe Reader requires more complex download, try alternative + Write-Host " [!] Adobe Reader requires manual download from Adobe website" + Write-Host " Please visit: https://get.adobe.com/reader/" + Write-Host " Or try: winget install Adobe.Acrobat.Reader.64-bit" + } +} else { + Write-Host " [SKIP] Adobe Reader already installed" +} + +Write-Host "" +Write-Host "=== Installation Summary ===" + +# Final verification +$apps = @( + @{Name="AnyDesk"; Process="AnyDesk"} + @{Name="LINE"; Process="LINE"} + @{Name="Chrome"; Process="chrome"} + @{Name="7-Zip"; Process="7zFM"} + @{Name="Adobe"; Process="AcroRd32"} +) + +foreach ($app in $apps) { + $installed = Test-AppInstalled -AppName $app.Name -ProcessName $app.Process + if ($installed) { + Write-Host "[OK] $($app.Name) - Installed" + } else { + Write-Host "[!] $($app.Name) - Not detected (may need manual verification)" + } +} + +Write-Host "" +Write-Host "[*] Application installation process completed" +Write-Host "Note: Some applications may require restart to appear in Start Menu" +Write-Host "All applications will be available to all users on this computer" \ No newline at end of file diff --git a/scripts/powershell/install-cports.ps1 b/scripts/powershell/install-cports.ps1 new file mode 100644 index 0000000..625743f --- /dev/null +++ b/scripts/powershell/install-cports.ps1 @@ -0,0 +1,66 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Stop' + +$url = 'https://www.nirsoft.net/utils/cports-x64.zip' +$installDir = 'C:\Program Files\NirSoft\CurrPorts' +$exeName = 'cports.exe' +$exePath = Join-Path $installDir $exeName + +# Idempotent +if (Test-Path $exePath) { + $ver = (Get-Item $exePath).VersionInfo.ProductVersion + Write-Host "[=] CurrPorts already installed: $exePath (v$ver)" +} else { + $tmpZip = Join-Path $env:TEMP 'cports-x64.zip' + $tmpDir = Join-Path $env:TEMP 'cports-extract' + if (Test-Path $tmpDir) { Remove-Item $tmpDir -Recurse -Force } + New-Item -ItemType Directory -Path $tmpDir | Out-Null + + Write-Host "[*] Downloading $url" + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + Invoke-WebRequest -Uri $url -OutFile $tmpZip -UseBasicParsing + if ((Get-Item $tmpZip).Length -lt 50KB) { throw "Downloaded zip too small" } + + Expand-Archive -Path $tmpZip -DestinationPath $tmpDir -Force + if (-not (Test-Path (Join-Path $tmpDir $exeName))) { + throw "$exeName not found in archive" + } + + New-Item -ItemType Directory -Force -Path $installDir | Out-Null + Copy-Item -Path (Join-Path $tmpDir '*') -Destination $installDir -Recurse -Force + + Remove-Item $tmpZip -Force -ErrorAction SilentlyContinue + Remove-Item $tmpDir -Recurse -Force -ErrorAction SilentlyContinue + + $ver = (Get-Item $exePath).VersionInfo.ProductVersion + Write-Host "[OK] Installed: $exePath (v$ver)" +} + +# Start Menu shortcut for all users (Common Start Menu) +$commonStart = 'C:\ProgramData\Microsoft\Windows\Start Menu\Programs' +$lnkCommon = Join-Path $commonStart 'CurrPorts.lnk' +$wsh = New-Object -ComObject WScript.Shell +$lnk = $wsh.CreateShortcut($lnkCommon) +$lnk.TargetPath = $exePath +$lnk.WorkingDirectory = $installDir +$lnk.IconLocation = "$exePath,0" +$lnk.Description = 'NirSoft CurrPorts - list open TCP/UDP ports' +$lnk.Save() +Write-Host "[*] Start Menu (All Users) shortcut: $lnkCommon" + +# Also desktop shortcut for the 'User' account (main daily-use account) +$userDesktop = 'C:\Users\user\Desktop' +if (Test-Path $userDesktop) { + $lnkUser = Join-Path $userDesktop 'CurrPorts.lnk' + $lnk2 = $wsh.CreateShortcut($lnkUser) + $lnk2.TargetPath = $exePath + $lnk2.WorkingDirectory = $installDir + $lnk2.IconLocation = "$exePath,0" + $lnk2.Save() + & icacls $lnkUser /grant 'User:F' /Q | Out-Null + Write-Host "[*] Desktop shortcut for User: $lnkUser" +} + +Write-Host "" +Write-Host "Launch from Start menu, or run directly:" +Write-Host " `"$exePath`"" diff --git a/scripts/powershell/install-essential-apps.ps1 b/scripts/powershell/install-essential-apps.ps1 new file mode 100644 index 0000000..aed5d72 --- /dev/null +++ b/scripts/powershell/install-essential-apps.ps1 @@ -0,0 +1,104 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Continue' + +Write-Host "[*] Installing essential applications" +Write-Host "Apps: AnyDesk, LINE, Chrome, 7zip, Adobe Reader" +Write-Host "" + +# Install via winget (preferred method) +$apps = @( + @{Name="AnyDesk"; WingetId="AnyDeskSoftwareGmbH.AnyDesk"} + @{Name="LINE"; WingetId="LINE.LINE"} + @{Name="Google Chrome"; WingetId="Google.Chrome"} + @{Name="7-Zip"; WingetId="7zip.7zip"} + @{Name="Adobe Reader"; WingetId="Adobe.Acrobat.Reader.64-bit"} +) + +$successCount = 0 +$totalApps = $apps.Count + +foreach ($app in $apps) { + Write-Host "Installing $($app.Name)..." + + try { + $result = & winget install $app.WingetId --accept-package-agreements --accept-source-agreements --silent 2>&1 + + if ($LASTEXITCODE -eq 0) { + Write-Host " [OK] $($app.Name) installed successfully" + $successCount++ + } else { + Write-Host " [SKIP] $($app.Name) may already be installed or failed" + # Check if already installed + $checkResult = & winget list $app.WingetId 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-Host " [INFO] $($app.Name) appears to be already installed" + $successCount++ + } + } + } catch { + Write-Host " [ERROR] Failed to install $($app.Name)" + + # Try alternative download for critical apps + if ($app.Name -eq "Google Chrome") { + Write-Host " Trying alternative Chrome download..." + try { + $chromeUrl = "https://dl.google.com/chrome/install/GoogleChromeStandaloneEnterprise64.msi" + $chromePath = "$env:TEMP\ChromeSetup.msi" + Invoke-WebRequest -Uri $chromeUrl -OutFile $chromePath -UseBasicParsing + & msiexec /i $chromePath /quiet /norestart + Write-Host " [OK] Chrome installed via direct download" + $successCount++ + } catch { + Write-Host " [ERROR] Chrome alternative install failed" + } + } + } + + Write-Host "" +} + +Write-Host "=== Installation Summary ===" +Write-Host "Successfully installed: $successCount / $totalApps applications" +Write-Host "" + +# Verify installations by checking common install locations +Write-Host "Verification:" + +$verifyApps = @( + @{Name="AnyDesk"; Path="C:\Program Files (x86)\AnyDesk\AnyDesk.exe"} + @{Name="LINE"; Path="C:\Users\$env:USERNAME\AppData\Local\LINE\bin\LineLauncher.exe"} + @{Name="Chrome"; Path="C:\Program Files\Google\Chrome\Application\chrome.exe"} + @{Name="7-Zip"; Path="C:\Program Files\7-Zip\7z.exe"} + @{Name="Adobe Reader"; Path="C:\Program Files (x86)\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe"} +) + +foreach ($app in $verifyApps) { + if (Test-Path $app.Path) { + Write-Host " [OK] $($app.Name) - Found at expected location" + } else { + # Try to find in registry + $found = $false + $regPaths = @( + "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*", + "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" + ) + + foreach ($regPath in $regPaths) { + $installed = Get-ItemProperty $regPath -ErrorAction SilentlyContinue | + Where-Object { $_.DisplayName -like "*$($app.Name.Split(' ')[0])*" } + if ($installed) { + Write-Host " [OK] $($app.Name) - Found in registry" + $found = $true + break + } + } + + if (-not $found) { + Write-Host " [!] $($app.Name) - Not detected" + } + } +} + +Write-Host "" +Write-Host "Installation completed!" +Write-Host "Note: Applications may require restart to appear in Start Menu" \ No newline at end of file diff --git a/scripts/powershell/install-nirsoft.ps1 b/scripts/powershell/install-nirsoft.ps1 new file mode 100644 index 0000000..fbc22a4 --- /dev/null +++ b/scripts/powershell/install-nirsoft.ps1 @@ -0,0 +1,102 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Stop' + +$configPath = Join-Path $PSScriptRoot 'nirsoft-config.json' +$cfg = Get-Content $configPath -Raw -Encoding UTF8 | ConvertFrom-Json + +$installRoot = $cfg.InstallRoot +$shortcutRoot = $cfg.ShortcutRoot + +New-Item -ItemType Directory -Force -Path $installRoot | Out-Null +New-Item -ItemType Directory -Force -Path $shortcutRoot | Out-Null + +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$wsh = New-Object -ComObject WScript.Shell + +function Install-OneTool { + param($tool) + $toolDir = Join-Path $installRoot $tool.Name + $exePath = Join-Path $toolDir $tool.Exe + + if (Test-Path $exePath) { + $ver = (Get-Item $exePath).VersionInfo.ProductVersion + Write-Host "[=] $($tool.Name) already installed (v$ver)" + } else { + Write-Host "[*] $($tool.Name) downloading $($tool.Url)" + $tmpZip = Join-Path $env:TEMP "$($tool.Name).zip" + $tmpDir = Join-Path $env:TEMP "$($tool.Name)-extract" + if (Test-Path $tmpDir) { Remove-Item $tmpDir -Recurse -Force } + New-Item -ItemType Directory -Path $tmpDir | Out-Null + + try { + Invoke-WebRequest -Uri $tool.Url -OutFile $tmpZip -UseBasicParsing + if ((Get-Item $tmpZip).Length -lt 20KB) { throw "download too small" } + Expand-Archive -Path $tmpZip -DestinationPath $tmpDir -Force + if (-not (Test-Path (Join-Path $tmpDir $tool.Exe))) { + # Some NirSoft archives nest the exe; search + $found = Get-ChildItem $tmpDir -Recurse -Filter $tool.Exe -ErrorAction SilentlyContinue | Select-Object -First 1 + if (-not $found) { throw "$($tool.Exe) not found in archive" } + Copy-Item -Path (Split-Path $found.FullName) -Destination $toolDir -Recurse -Force + } else { + New-Item -ItemType Directory -Force -Path $toolDir | Out-Null + Copy-Item -Path (Join-Path $tmpDir '*') -Destination $toolDir -Recurse -Force + } + $ver = (Get-Item $exePath).VersionInfo.ProductVersion + Write-Host "[OK] $($tool.Name) v$ver -> $toolDir" + } finally { + Remove-Item $tmpZip -Force -ErrorAction SilentlyContinue + Remove-Item $tmpDir -Recurse -Force -ErrorAction SilentlyContinue + } + } + + # Admin-only Start Menu shortcut under NirSoft\ + $lnk = $wsh.CreateShortcut((Join-Path $shortcutRoot "$($tool.Name).lnk")) + $lnk.TargetPath = $exePath + $lnk.WorkingDirectory = $toolDir + $lnk.IconLocation = "$exePath,0" + $lnk.Save() +} + +# --- Install all tools --- +foreach ($t in $cfg.Tools) { + try { Install-OneTool -tool $t } + catch { Write-Host "[!] $($t.Name) failed: $($_.Exception.Message)" } +} + +# --- Reorganize existing CurrPorts shortcuts (hide from User) --- +Write-Host "" +Write-Host "[*] Reorganizing CurrPorts shortcuts (hide from User)" + +$cpExe = Join-Path $installRoot 'CurrPorts\cports.exe' +if (Test-Path $cpExe) { + # Create in Admin's NirSoft folder + $lnk = $wsh.CreateShortcut((Join-Path $shortcutRoot 'CurrPorts.lnk')) + $lnk.TargetPath = $cpExe + $lnk.WorkingDirectory = Split-Path $cpExe + $lnk.IconLocation = "$cpExe,0" + $lnk.Save() + Write-Host " - added to Admin's NirSoft Start Menu" +} + +# Remove visible CurrPorts shortcuts that the User would see +$visibleShortcuts = @( + 'C:\ProgramData\Microsoft\Windows\Start Menu\Programs\CurrPorts.lnk', + 'C:\Users\user\Desktop\CurrPorts.lnk', + 'C:\Users\User\Desktop\CurrPorts.lnk' +) +foreach ($s in $visibleShortcuts) { + if (Test-Path $s) { + Remove-Item $s -Force -ErrorAction SilentlyContinue + Write-Host " - removed $s" + } +} + +Write-Host "" +Write-Host "===== Summary =====" +Write-Host "Installed to: $installRoot" +Write-Host "Shortcuts: $shortcutRoot (Admin's Start Menu only, not visible to 'User')" +Write-Host "" +Write-Host "All tools support CSV/text output via /scomma /stext /shtml /sxml flags, e.g.:" +Write-Host " cports.exe /scomma ports.csv" +Write-Host " InstalledAppsView.exe /scomma apps.csv" +Write-Host " LastActivityView.exe /scomma activity.csv" diff --git a/scripts/powershell/install-remaining-apps.ps1 b/scripts/powershell/install-remaining-apps.ps1 new file mode 100644 index 0000000..7c4f6f2 --- /dev/null +++ b/scripts/powershell/install-remaining-apps.ps1 @@ -0,0 +1,187 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Continue' + +Write-Host "[*] Installing remaining applications with Windows Defender bypass" +Write-Host "Apps: AnyDesk, LINE, Adobe Reader" +Write-Host "" + +# Check if running as administrator +$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +if (-not $isAdmin) { + Write-Host "[!] ERROR: This script must be run as Administrator" + exit 1 +} + +$downloadFolder = "$env:TEMP\DirectAppInstalls" +New-Item -ItemType Directory -Force -Path $downloadFolder | Out-Null + +# Function to temporarily disable Windows Defender +function Disable-WindowsDefender { + Write-Host "[*] Temporarily disabling Windows Defender real-time protection..." + try { + Set-MpPreference -DisableRealtimeMonitoring $true + Write-Host " [OK] Windows Defender real-time protection disabled" + return $true + } catch { + Write-Host " [!] Failed to disable Windows Defender: $($_.Exception.Message)" + Write-Host " Continuing with installation anyway..." + return $false + } +} + +# Function to re-enable Windows Defender +function Enable-WindowsDefender { + Write-Host "[*] Re-enabling Windows Defender real-time protection..." + try { + Set-MpPreference -DisableRealtimeMonitoring $false + Write-Host " [OK] Windows Defender real-time protection re-enabled" + } catch { + Write-Host " [!] Failed to re-enable Windows Defender: $($_.Exception.Message)" + } +} + +# Function to install application +function Install-DirectApp { + param( + [string]$Name, + [string]$Url, + [string]$FileName, + [string]$InstallArgs, + [int]$TimeoutMinutes = 5 + ) + + Write-Host "Installing $Name..." + Write-Host " URL: $Url" + + $downloadPath = Join-Path $downloadFolder $FileName + $success = $false + + try { + # Download + Write-Host " Downloading..." + Invoke-WebRequest -Uri $Url -OutFile $downloadPath -UseBasicParsing -TimeoutSec 120 + + if (Test-Path $downloadPath) { + $fileSize = (Get-Item $downloadPath).Length / 1MB + Write-Host " Downloaded: $([math]::Round($fileSize, 2)) MB" + + # Install + Write-Host " Installing with args: $InstallArgs" + + if ($FileName.EndsWith(".msi")) { + $process = Start-Process -FilePath "msiexec" -ArgumentList "/i", $downloadPath, $InstallArgs -Wait -PassThru + } else { + $argList = $InstallArgs.Split(' ') + $downloadPath + $process = Start-Process -FilePath $downloadPath -ArgumentList $InstallArgs -Wait -PassThru -WindowStyle Hidden + } + + Write-Host " Process exit code: $($process.ExitCode)" + + if ($process.ExitCode -eq 0 -or $process.ExitCode -eq 3010) { + Write-Host " [OK] $Name installation completed successfully" + $success = $true + } else { + Write-Host " [!] $Name installation may have failed (exit code: $($process.ExitCode))" + # Some installers return non-zero codes even on success, so we'll verify later + } + + } else { + Write-Host " [!] Download failed - file not found" + } + + } catch { + Write-Host " [ERROR] $Name installation failed: $($_.Exception.Message)" + } finally { + # Clean up download + if (Test-Path $downloadPath) { + Remove-Item $downloadPath -Force -ErrorAction SilentlyContinue + } + } + + return $success +} + +# Applications to install +$apps = @( + @{ + Name = "AnyDesk" + Url = "https://download.anydesk.com/AnyDesk.msi" + FileName = "AnyDesk.msi" + InstallArgs = "/quiet /norestart" + }, + @{ + Name = "LINE" + Url = "https://desktop.line-scdn.net/win/new/LineInst.exe" + FileName = "LineInst.exe" + InstallArgs = "/S" + }, + @{ + Name = "Adobe Reader" + Url = "https://ardownload2.adobe.com/pub/adobe/reader/win/AcrobatDC/2300820555/AcroRdrDC2300820555_en_US.exe" + FileName = "AdobeReader.exe" + InstallArgs = "/sAll /rs /msi EULA_ACCEPT=YES" + } +) + +Write-Host "=== Starting Installation Process ===" +Write-Host "" + +# Disable Windows Defender +$defenderDisabled = Disable-WindowsDefender + +$installedCount = 0 +$totalApps = $apps.Count + +try { + foreach ($app in $apps) { + Write-Host "[$($installedCount + 1)/$totalApps] $($app.Name)" + + if (Install-DirectApp -Name $app.Name -Url $app.Url -FileName $app.FileName -InstallArgs $app.InstallArgs) { + $installedCount++ + } + + Write-Host "" + Start-Sleep -Seconds 3 + } + +} finally { + # Always re-enable Windows Defender + if ($defenderDisabled) { + Enable-WindowsDefender + } +} + +# Clean up download folder +Remove-Item $downloadFolder -Recurse -Force -ErrorAction SilentlyContinue + +Write-Host "=== Installation Summary ===" +Write-Host "Successfully installed: $installedCount / $totalApps applications" +Write-Host "" + +# Final verification +Write-Host "=== Verification ===" + +$verificationPaths = @( + @{Name="AnyDesk"; Paths=@("C:\Program Files (x86)\AnyDesk\AnyDesk.exe", "C:\Program Files\AnyDesk\AnyDesk.exe")}, + @{Name="LINE"; Paths=@("C:\Users\$env:USERNAME\AppData\Local\LINE\bin\LineLauncher.exe", "C:\Program Files\LINE\bin\LineLauncher.exe")}, + @{Name="Adobe Reader"; Paths=@("C:\Program Files (x86)\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe", "C:\Program Files\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe")} +) + +foreach ($check in $verificationPaths) { + $found = $false + foreach ($path in $check.Paths) { + if (Test-Path $path) { + Write-Host "[OK] $($check.Name) - Found at $path" + $found = $true + break + } + } + if (-not $found) { + Write-Host "[!] $($check.Name) - Not found (may need restart or manual verification)" + } +} + +Write-Host "" +Write-Host "Installation completed!" +Write-Host "Note: Some applications may require restart to appear in Start Menu" +Write-Host "Windows Defender real-time protection has been restored" \ No newline at end of file diff --git a/scripts/powershell/install-telegram.ps1 b/scripts/powershell/install-telegram.ps1 new file mode 100644 index 0000000..8c50a4f --- /dev/null +++ b/scripts/powershell/install-telegram.ps1 @@ -0,0 +1,102 @@ +param( + [string]$TargetUser = 'user' +) + +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Stop' + +$targetProfile = "C:\Users\$TargetUser" +if (-not (Test-Path $targetProfile)) { throw "Windows user '$TargetUser' not found ($targetProfile missing)" } + +$installDir = Join-Path $targetProfile 'AppData\Roaming\Telegram Desktop' +$startMenuDir = Join-Path $targetProfile 'AppData\Roaming\Microsoft\Windows\Start Menu\Programs' +$desktopDir = Join-Path $targetProfile 'Desktop' + +function Get-TelegramVersion($dir) { + $exe = Join-Path $dir 'Telegram.exe' + if (Test-Path $exe) { return (Get-Item $exe).VersionInfo.ProductVersion } + return $null +} + +# Already installed at target? +$existingVer = Get-TelegramVersion $installDir +if ($existingVer) { + Write-Host "[=] Already installed for '$TargetUser': Telegram $existingVer at $installDir" + exit 0 +} + +# Clean up any stray Admin-scoped install that we may have created earlier +$adminDir = 'C:\Users\Admin\AppData\Roaming\Telegram Desktop' +$adminUninst = Join-Path $adminDir 'unins000.exe' +if ((Test-Path $adminUninst) -and ($TargetUser -ne 'Admin')) { + Write-Host "[*] Removing stray Admin-scoped install at $adminDir" + Get-Process Telegram,Updater -ErrorAction SilentlyContinue | Stop-Process -Force + $p = Start-Process -FilePath $adminUninst -ArgumentList '/VERYSILENT','/SUPPRESSMSGBOXES','/NORESTART' -Wait -PassThru + if ($p.ExitCode -ne 0) { Write-Host "[!] Uninstall exited $($p.ExitCode), continuing anyway" } + + # Remove Admin's HKCU uninstall key (best-effort) + Get-Item 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' -ErrorAction SilentlyContinue | + Where-Object { (Get-ItemProperty $_.PSPath).DisplayName -match 'Telegram' } | + ForEach-Object { Remove-Item $_.PSPath -Force -Recurse -ErrorAction SilentlyContinue } + # Remove Admin's start menu shortcut + $adminLnk = 'C:\Users\Admin\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Telegram.lnk' + Remove-Item $adminLnk -ErrorAction SilentlyContinue + Start-Sleep -Seconds 2 +} + +# Download installer +$url = 'https://telegram.org/dl/desktop/win64' +$dst = Join-Path $env:TEMP 'telegram-setup.exe' +Write-Host "[*] Downloading installer -> $dst" +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +Invoke-WebRequest -Uri $url -OutFile $dst -UseBasicParsing +if (-not (Test-Path $dst) -or (Get-Item $dst).Length -lt 10MB) { + throw "Downloaded file looks wrong (< 10 MB)" +} + +# Install into target user's AppData. /NOICONS = no shortcuts (we'll create them in target's profile). +New-Item -ItemType Directory -Force -Path $installDir | Out-Null +$argStr = '/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- /NOICONS /NOLAUNCH /DIR="' + $installDir + '"' +Write-Host "[*] Installing to $installDir" +$p = Start-Process -FilePath $dst -ArgumentList $argStr -Wait -PassThru +Remove-Item $dst -ErrorAction SilentlyContinue +if ($p.ExitCode -ne 0) { throw "Installer exited $($p.ExitCode)" } + +# Verify binary appeared +$finalVer = Get-TelegramVersion $installDir +if (-not $finalVer) { throw "Telegram.exe not found at $installDir after install" } + +# Give target user full control over the install tree +Write-Host "[*] Adjusting ACL on $installDir for '$TargetUser'" +& icacls $installDir /grant "${TargetUser}:(OI)(CI)F" /T /Q | Out-Null + +# Create Start Menu shortcut in target user's profile +New-Item -ItemType Directory -Force -Path $startMenuDir | Out-Null +$wsh = New-Object -ComObject WScript.Shell +$lnk = $wsh.CreateShortcut((Join-Path $startMenuDir 'Telegram.lnk')) +$lnk.TargetPath = Join-Path $installDir 'Telegram.exe' +$lnk.WorkingDirectory = $installDir +$lnk.IconLocation = (Join-Path $installDir 'Telegram.exe') + ',0' +$lnk.Save() +Write-Host "[*] Start menu shortcut: $(Join-Path $startMenuDir 'Telegram.lnk')" + +# Desktop shortcut +if (Test-Path $desktopDir) { + $dlnk = $wsh.CreateShortcut((Join-Path $desktopDir 'Telegram.lnk')) + $dlnk.TargetPath = Join-Path $installDir 'Telegram.exe' + $dlnk.WorkingDirectory = $installDir + $dlnk.IconLocation = (Join-Path $installDir 'Telegram.exe') + ',0' + $dlnk.Save() + Write-Host "[*] Desktop shortcut: $(Join-Path $desktopDir 'Telegram.lnk')" +} + +# Fix ACL on the shortcuts too +& icacls (Join-Path $startMenuDir 'Telegram.lnk') /grant "${TargetUser}:F" /Q | Out-Null +if (Test-Path (Join-Path $desktopDir 'Telegram.lnk')) { + & icacls (Join-Path $desktopDir 'Telegram.lnk') /grant "${TargetUser}:F" /Q | Out-Null +} + +Write-Host "" +Write-Host "[OK] Telegram $finalVer installed for '$TargetUser'" +Write-Host " Location: $installDir" +Write-Host " Data (tdata) will be created beside the exe on first launch." diff --git a/scripts/powershell/install-thunderbird.ps1 b/scripts/powershell/install-thunderbird.ps1 new file mode 100644 index 0000000..b5a3468 --- /dev/null +++ b/scripts/powershell/install-thunderbird.ps1 @@ -0,0 +1,73 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Stop' + +function Test-Installed { + $keys = @( + 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*', + 'HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' + ) + Get-ItemProperty $keys -ErrorAction SilentlyContinue | + Where-Object { $_.DisplayName -match 'Thunderbird' } | + Select-Object -First 1 DisplayName,DisplayVersion,InstallLocation +} + +$existing = Test-Installed +if ($existing) { + Write-Host "[=] Thunderbird already installed: $($existing.DisplayName) $($existing.DisplayVersion)" + Write-Host " Location: $($existing.InstallLocation)" + exit 0 +} + +$installed = $false + +# --- Attempt 1: winget --- +$winget = Get-Command winget -ErrorAction SilentlyContinue +if ($winget) { + Write-Host "[*] winget found, installing Mozilla.Thunderbird" + try { + & winget install --id Mozilla.Thunderbird -e --silent ` + --accept-source-agreements --accept-package-agreements ` + --scope machine 2>&1 | Write-Host + if ($LASTEXITCODE -eq 0) { $installed = $true } + else { Write-Host "[!] winget exited $LASTEXITCODE, falling back to direct download" } + } catch { + Write-Host "[!] winget failed: $_" + } +} else { + Write-Host "[!] winget not available, using direct download" +} + +# --- Attempt 2: direct download from Mozilla CDN --- +if (-not $installed) { + $url = 'https://download.mozilla.org/?product=thunderbird-latest&os=win64&lang=zh-TW' + $dst = Join-Path $env:TEMP 'thunderbird-setup.exe' + + Write-Host "[*] Downloading installer -> $dst" + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + Invoke-WebRequest -Uri $url -OutFile $dst -UseBasicParsing + + if (-not (Test-Path $dst) -or (Get-Item $dst).Length -lt 10MB) { + throw "Downloaded file looks wrong (size < 10 MB)" + } + + Write-Host "[*] Running silent install" + $proc = Start-Process -FilePath $dst -ArgumentList '/S' -Wait -PassThru + if ($proc.ExitCode -ne 0) { + throw "Installer exited with code $($proc.ExitCode)" + } + Remove-Item $dst -ErrorAction SilentlyContinue + $installed = $true +} + +# --- Verify --- +Start-Sleep -Seconds 2 +$after = Test-Installed +if ($after) { + Write-Host "" + Write-Host "[OK] Installed: $($after.DisplayName) $($after.DisplayVersion)" + Write-Host " Location: $($after.InstallLocation)" + exit 0 +} else { + Write-Host "[FAIL] Thunderbird was not detected in uninstall registry after install" + exit 1 +} diff --git a/scripts/powershell/investigate-failures.ps1 b/scripts/powershell/investigate-failures.ps1 new file mode 100644 index 0000000..ba64a18 --- /dev/null +++ b/scripts/powershell/investigate-failures.ps1 @@ -0,0 +1,135 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Continue' + +Write-Host "[*] Investigating application installation failures" +Write-Host "" + +# Error code analysis +Write-Host "=== Error Code Analysis ===" +Write-Host "Error code -1978335212 (0x8A150054) typically indicates:" +Write-Host "- APPINSTALLER_CLI_ERROR_INSTALLER_FAILED" +Write-Host "- The package installer failed during execution" +Write-Host "" + +Write-Host "[1] Checking system requirements and dependencies..." + +# Check Windows version +$osInfo = Get-ComputerInfo | Select-Object WindowsProductName, WindowsVersion, WindowsBuildLabEx +Write-Host "OS: $($osInfo.WindowsProductName)" +Write-Host "Version: $($osInfo.WindowsVersion)" +Write-Host "Build: $($osInfo.WindowsBuildLabEx)" + +# Check available disk space +$disk = Get-WmiObject -Class Win32_LogicalDisk | Where-Object {$_.DeviceID -eq "C:"} +$freeSpaceGB = [math]::Round($disk.FreeSpace / 1GB, 2) +Write-Host "Free space on C: drive: $freeSpaceGB GB" + +Write-Host "" +Write-Host "[2] Checking for conflicting installations..." + +# Check if any versions are already partially installed +$installedApps = Get-WmiObject -Class Win32_Product | Where-Object { + $_.Name -like "*AnyDesk*" -or + $_.Name -like "*LINE*" -or + $_.Name -like "*Adobe*Reader*" +} | Select-Object Name, Version, IdentifyingNumber + +if ($installedApps) { + Write-Host "Found existing installations:" + foreach ($app in $installedApps) { + Write-Host " - $($app.Name) v$($app.Version)" + } +} else { + Write-Host "No conflicting installations found" +} + +Write-Host "" +Write-Host "[3] Testing individual app availability..." + +$failedApps = @( + @{Name="AnyDesk"; Id="AnyDeskSoftwareGmbH.AnyDesk"}, + @{Name="LINE"; Id="LINE.LINE"}, + @{Name="Adobe Reader"; Id="Adobe.Acrobat.Reader.64-bit"} +) + +foreach ($app in $failedApps) { + Write-Host "Testing $($app.Name)..." + + # Check if app is available in winget + try { + $searchResult = & winget show $app.Id --source winget 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-Host " [OK] Package found in winget" + + # Extract some key info + $lines = $searchResult -split "`n" + foreach ($line in $lines) { + if ($line -like "*Version:*" -or $line -like "*Publisher:*" -or $line -like "*Package Url:*") { + Write-Host " $line" + } + } + } else { + Write-Host " [!] Package not found or access denied" + } + } catch { + Write-Host " [!] Search failed: $($_.Exception.Message)" + } + + Write-Host "" +} + +Write-Host "[4] Checking system policies and restrictions..." + +# Check execution policy +$execPolicy = Get-ExecutionPolicy -Scope LocalMachine +Write-Host "Execution Policy: $execPolicy" + +# Check if running as administrator +$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +Write-Host "Running as Administrator: $isAdmin" + +# Check Windows Defender status +try { + $defender = Get-MpPreference -ErrorAction SilentlyContinue + if ($defender) { + Write-Host "Windows Defender Real-time Protection: $($defender.DisableRealtimeMonitoring -eq $false)" + } +} catch { + Write-Host "Cannot check Windows Defender status" +} + +Write-Host "" +Write-Host "[5] Testing direct download availability..." + +$downloadTests = @( + @{Name="AnyDesk"; Url="https://download.anydesk.com/AnyDesk.exe"}, + @{Name="LINE"; Url="https://desktop.line-scdn.net/win/new/LineInst.exe"}, + @{Name="Adobe Reader"; Url="https://ardownload2.adobe.com/pub/adobe/reader/win/AcrobatDC/2300820555/AcroRdrDC2300820555_en_US.exe"} +) + +foreach ($test in $downloadTests) { + Write-Host "Testing $($test.Name) direct download..." + try { + $webRequest = Invoke-WebRequest -Uri $test.Url -Method Head -UseBasicParsing -TimeoutSec 10 + Write-Host " [OK] Direct download available (Status: $($webRequest.StatusCode))" + } catch { + Write-Host " [!] Direct download failed: $($_.Exception.Message)" + } +} + +Write-Host "" +Write-Host "=== Investigation Summary ===" +Write-Host "" +Write-Host "Possible reasons for failures:" +Write-Host "1. Package installer requirements not met (missing dependencies)" +Write-Host "2. Antivirus/Windows Defender blocking installation" +Write-Host "3. Insufficient permissions despite administrator status" +Write-Host "4. Network connectivity issues to specific download servers" +Write-Host "5. Package conflicts with existing system components" +Write-Host "6. Windows version compatibility issues" +Write-Host "" +Write-Host "Recommended solutions:" +Write-Host "1. Try direct installer downloads with elevated permissions" +Write-Host "2. Temporarily disable real-time protection during installation" +Write-Host "3. Use alternative installation sources or older versions" +Write-Host "4. Install dependencies manually before main applications" \ No newline at end of file diff --git a/scripts/powershell/map-anonymous-drive.ps1 b/scripts/powershell/map-anonymous-drive.ps1 new file mode 100644 index 0000000..ed66ea0 --- /dev/null +++ b/scripts/powershell/map-anonymous-drive.ps1 @@ -0,0 +1,145 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Continue' + +Write-Host "[*] Mapping R: drive with anonymous access (no credentials)" + +$networkPath = "\\192.168.88.231\RD" +$driveLetter = "R:" + +# First, remove any existing R: mapping +Write-Host "[*] Cleaning up existing mappings" +try { + & net use $driveLetter /delete /yes 2>$null + Write-Host " Cleared any existing R: mapping" +} catch { + Write-Host " No existing mapping to clear" +} + +Write-Host "[*] Testing anonymous access methods" + +# Method 1: Simple net use without credentials +Write-Host "[1] Trying simple net use..." +try { + $result = & net use $driveLetter $networkPath /persistent:yes + if ($LASTEXITCODE -eq 0) { + Write-Host "[OK] Method 1 successful!" + $success = $true + } else { + Write-Host " Method 1 failed: $result" + $success = $false + } +} catch { + Write-Host " Method 1 exception: $($_.Exception.Message)" + $success = $false +} + +# Method 2: Explicit guest access +if (-not $success) { + Write-Host "[2] Trying guest access..." + try { + $result = & net use $driveLetter $networkPath /user:guest "" /persistent:yes + if ($LASTEXITCODE -eq 0) { + Write-Host "[OK] Method 2 successful!" + $success = $true + } else { + Write-Host " Method 2 failed: $result" + } + } catch { + Write-Host " Method 2 exception: $($_.Exception.Message)" + } +} + +# Method 3: Empty username +if (-not $success) { + Write-Host "[3] Trying empty username..." + try { + $result = & net use $driveLetter $networkPath /user:"" "" /persistent:yes + if ($LASTEXITCODE -eq 0) { + Write-Host "[OK] Method 3 successful!" + $success = $true + } else { + Write-Host " Method 3 failed: $result" + } + } catch { + Write-Host " Method 3 exception: $($_.Exception.Message)" + } +} + +# Method 4: Using PowerShell New-PSDrive +if (-not $success) { + Write-Host "[4] Trying PowerShell PSDrive..." + try { + New-PSDrive -Name "R" -PSProvider FileSystem -Root $networkPath -Persist -ErrorAction Stop + Write-Host "[OK] Method 4 successful!" + $success = $true + } catch { + Write-Host " Method 4 exception: $($_.Exception.Message)" + } +} + +# Verify the mapping +Write-Host "[*] Verifying drive mapping" + +Start-Sleep -Seconds 2 + +$driveCheck = Get-WmiObject -Class Win32_LogicalDisk | Where-Object { $_.DeviceID -eq $driveLetter } + +if ($driveCheck -and $driveCheck.DriveType -eq 4) { + Write-Host "[OK] Network drive R: successfully mapped!" + Write-Host " Device: $($driveCheck.DeviceID)" + Write-Host " Provider: $($driveCheck.ProviderName)" + Write-Host " Type: Network Drive" + + # Test accessibility + try { + if (Test-Path "${driveLetter}\") { + Write-Host "[OK] Drive R: is accessible" + + # List contents + $items = Get-ChildItem "${driveLetter}\" -ErrorAction SilentlyContinue + if ($items) { + Write-Host " Contents: $($items.Count) items found" + Write-Host " Sample items:" + $items | Select-Object -First 3 | ForEach-Object { + Write-Host " - $($_.Name) ($($_.Mode))" + } + } else { + Write-Host " Directory appears empty" + } + } else { + Write-Host "[!] Drive mapped but not accessible" + } + } catch { + Write-Host "[!] Error testing accessibility: $($_.Exception.Message)" + } + +} else { + Write-Host "[!] Drive mapping verification failed" + + # Show current network drives + $networkDrives = Get-WmiObject -Class Win32_LogicalDisk | Where-Object { $_.DriveType -eq 4 } + if ($networkDrives) { + Write-Host "Current network drives:" + foreach ($drive in $networkDrives) { + Write-Host " $($drive.DeviceID) -> $($drive.ProviderName)" + } + } else { + Write-Host "No network drives currently mapped" + } + + Write-Host "" + Write-Host "Manual verification:" + Write-Host "1. Open File Explorer" + Write-Host "2. Check if R: drive appears under 'This PC'" + Write-Host "3. Try accessing \\192.168.88.231\RD directly" +} + +Write-Host "" +Write-Host "[*] Drive mapping process completed" + +# Clean up the desktop batch file since we don't need credentials +$batchFile = "C:\Users\User\Desktop\Map-Drive-R.bat" +if (Test-Path $batchFile) { + Remove-Item $batchFile -Force -ErrorAction SilentlyContinue + Write-Host " Removed credential batch file (not needed)" +} \ No newline at end of file diff --git a/scripts/powershell/map-drive-r.ps1 b/scripts/powershell/map-drive-r.ps1 new file mode 100644 index 0000000..a47c046 --- /dev/null +++ b/scripts/powershell/map-drive-r.ps1 @@ -0,0 +1,98 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Continue' + +Write-Host "[*] Mapping network drive R: with persistent connection" + +$networkPath = "\\192.168.88.231\RD" +$driveLetter = "R:" + +Write-Host "[*] Creating persistent network drive mapping" +Write-Host " Target: $networkPath" +Write-Host " Drive: $driveLetter" + +# Method 1: Try mapping without credentials first (anonymous/guest access) +Write-Host "[*] Attempting anonymous mapping..." + +try { + $result = & net use $driveLetter $networkPath /persistent:yes 2>&1 + + if ($LASTEXITCODE -eq 0) { + Write-Host "[OK] Anonymous mapping successful!" + } else { + Write-Host " Anonymous mapping failed, will require manual credentials" + } +} catch { + Write-Host " Exception during anonymous mapping" +} + +# Check if mapping was successful +$driveCheck = Get-WmiObject -Class Win32_LogicalDisk | Where-Object { $_.DeviceID -eq $driveLetter } + +if ($driveCheck -and $driveCheck.DriveType -eq 4) { + Write-Host "[OK] Network drive R: successfully mapped!" + Write-Host " Provider: $($driveCheck.ProviderName)" + + # Test access + try { + $testAccess = Test-Path "${driveLetter}\" + if ($testAccess) { + Write-Host "[OK] Drive R: is accessible" + + # Try to list contents + $items = Get-ChildItem "${driveLetter}\" -ErrorAction SilentlyContinue | Select-Object -First 3 + if ($items) { + Write-Host " Found $($items.Count) items in the drive" + } else { + Write-Host " Drive appears empty or no read permission" + } + } else { + Write-Host "[!] Drive R: mapped but not accessible" + } + } catch { + Write-Host "[!] Cannot test drive access: $($_.Exception.Message)" + } + +} else { + Write-Host "[!] Network drive mapping incomplete" + Write-Host "" + Write-Host "Manual mapping required:" + Write-Host "1. Open File Explorer" + Write-Host "2. Right-click 'This PC' > Map network drive" + Write-Host "3. Drive: R:" + Write-Host "4. Folder: \\192.168.88.231\RD" + Write-Host "5. Check 'Reconnect at sign-in'" + Write-Host "6. Enter credentials when prompted" + Write-Host "" + Write-Host "Or use command line:" + Write-Host "net use R: \\192.168.88.231\RD /user:username /persistent:yes" +} + +# Create a batch file for easy manual mapping +$batchContent = @" +@echo off +echo Mapping network drive R: to \\192.168.88.231\RD +echo. +echo If prompted, enter the username and password for 192.168.88.231 +echo. +net use R: \\192.168.88.231\RD /persistent:yes +echo. +if %errorlevel%==0 ( + echo Success! Drive R: mapped successfully. + explorer R:\ +) else ( + echo Mapping failed. Please check credentials and network connectivity. +) +pause +"@ + +$batchPath = "C:\Users\User\Desktop\Map-Drive-R.bat" +$batchContent | Out-File -FilePath $batchPath -Encoding ASCII +Write-Host "[*] Created manual mapping batch file: Map-Drive-R.bat" + +Write-Host "" +Write-Host "Network drive setup complete!" +Write-Host "" +Write-Host "If R: drive is not visible:" +Write-Host "1. Double-click 'Map-Drive-R.bat' on User's desktop" +Write-Host "2. Enter network credentials when prompted" +Write-Host "3. Drive R: will appear in File Explorer" \ No newline at end of file diff --git a/scripts/powershell/nirsoft-dump.ps1 b/scripts/powershell/nirsoft-dump.ps1 new file mode 100644 index 0000000..6a04d33 --- /dev/null +++ b/scripts/powershell/nirsoft-dump.ps1 @@ -0,0 +1,96 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Continue' + +$ns = 'C:\Program Files\NirSoft' +$stamp = Get-Date -Format 'yyyyMMdd-HHmmss' +$hostname = $env:COMPUTERNAME +$outDir = Join-Path $env:TEMP "nirsoft-dump-$hostname-$stamp" +New-Item -ItemType Directory -Force -Path $outDir | Out-Null + +function Run-Tool { + param( + [string]$Name, + [string]$Exe, + [string[]]$ArgList, + [int]$TimeoutSec = 60 + ) + $path = Join-Path $ns "$Name\$Exe" + if (-not (Test-Path $path)) { + Write-Host "[skip] $Name not installed" + return + } + Write-Host "[*] $Name" + try { + $p = Start-Process -FilePath $path -ArgumentList $ArgList -Wait -PassThru -WindowStyle Hidden + if ($p.ExitCode -ne 0) { Write-Host " (exit $($p.ExitCode))" } + } catch { + Write-Host " [!] $($_.Exception.Message)" + } +} + +# === NirSoft CSV dumps === +Run-Tool 'BlueScreenView' 'BlueScreenView.exe' @('/scomma', (Join-Path $outDir 'bluescreen.csv')) +Run-Tool 'LastActivityView' 'LastActivityView.exe' @('/scomma', (Join-Path $outDir 'lastactivity.csv')) +Run-Tool 'WhatInStartup' 'WhatInStartup.exe' @('/scomma', (Join-Path $outDir 'startup.csv')) +Run-Tool 'OpenedFilesView' 'OpenedFilesView.exe' @('/scomma', (Join-Path $outDir 'opened-files.csv')) +Run-Tool 'InstalledAppView' 'InstalledAppView.exe' @('/scomma', (Join-Path $outDir 'installed-apps.csv')) +Run-Tool 'DevManView' 'DevManView.exe' @('/scomma', (Join-Path $outDir 'devices.csv')) +Run-Tool 'USBDeview' 'USBDeview.exe' @('/scomma', (Join-Path $outDir 'usb-history.csv')) +Run-Tool 'TurnedOnTimesView' 'TurnedOnTimesView.exe' @('/scomma', (Join-Path $outDir 'uptime.csv')) +Run-Tool 'CurrPorts' 'cports.exe' @('/scomma', (Join-Path $outDir 'ports.csv')) +Run-Tool 'WifiInfoView' 'WifiInfoView.exe' @('/scomma', (Join-Path $outDir 'wifi-nearby.csv')) +Run-Tool 'WirelessNetView' 'WirelessNetView.exe' @('/scomma', (Join-Path $outDir 'wifi-tracking.csv')) +Run-Tool 'DNSDataView' 'DNSDataView.exe' @('/scomma', (Join-Path $outDir 'dns-cache.csv')) + +# === Native Windows snapshots === +Write-Host "" +Write-Host "[*] Native Windows snapshots" + +& systeminfo.exe | Out-File (Join-Path $outDir 'systeminfo.txt') -Encoding UTF8 +& ipconfig.exe /all | Out-File (Join-Path $outDir 'ipconfig.txt') -Encoding UTF8 +& netstat.exe -ano | Out-File (Join-Path $outDir 'netstat.txt') -Encoding UTF8 + +Get-Process | Select-Object Id,ProcessName,CPU,WS,@{n='WS_MB';e={[math]::Round($_.WS/1MB,1)}},Path,StartTime | + Export-Csv -Path (Join-Path $outDir 'processes.csv') -NoTypeInformation -Encoding UTF8 + +Get-Service | Select-Object Name,DisplayName,Status,StartType | + Export-Csv -Path (Join-Path $outDir 'services.csv') -NoTypeInformation -Encoding UTF8 + +Get-ScheduledTask | Select-Object TaskPath,TaskName,State,Author | + Export-Csv -Path (Join-Path $outDir 'scheduled-tasks.csv') -NoTypeInformation -Encoding UTF8 + +# Recent error/warning events (System + Application, last 24h) +Get-WinEvent -FilterHashtable @{LogName='System'; Level=1,2,3; StartTime=(Get-Date).AddHours(-24)} -MaxEvents 500 -ErrorAction SilentlyContinue | + Select-Object TimeCreated,Id,LevelDisplayName,ProviderName,Message | + Export-Csv -Path (Join-Path $outDir 'events-system-24h.csv') -NoTypeInformation -Encoding UTF8 +Get-WinEvent -FilterHashtable @{LogName='Application'; Level=1,2,3; StartTime=(Get-Date).AddHours(-24)} -MaxEvents 500 -ErrorAction SilentlyContinue | + Select-Object TimeCreated,Id,LevelDisplayName,ProviderName,Message | + Export-Csv -Path (Join-Path $outDir 'events-application-24h.csv') -NoTypeInformation -Encoding UTF8 + +# === Summary file === +@" +=== Nirsoft + System Dump === +Host: $hostname +Timestamp: $stamp +User running: $env:USERDOMAIN\$env:USERNAME +OS: $((Get-CimInstance Win32_OperatingSystem).Caption) $((Get-CimInstance Win32_OperatingSystem).Version) +Boot time: $((Get-CimInstance Win32_OperatingSystem).LastBootUpTime) +Uptime: $((Get-Date) - (Get-CimInstance Win32_OperatingSystem).LastBootUpTime) + +Files in this archive: +$((Get-ChildItem $outDir | Sort-Object Name | ForEach-Object { " - $($_.Name) ($(if ($_.Length) { '{0:N0}' -f $_.Length } else { '-' }) bytes)" }) -join "`r`n") +"@ | Out-File (Join-Path $outDir '_summary.txt') -Encoding UTF8 + +# === Zip and announce === +$zipPath = "$outDir.zip" +Compress-Archive -Path (Join-Path $outDir '*') -DestinationPath $zipPath -Force +$zipSize = (Get-Item $zipPath).Length + +Write-Host "" +Write-Host "=====================================" +Write-Host "DUMP_ZIP: $zipPath" +Write-Host "DUMP_SIZE: $zipSize" +Write-Host "=====================================" + +# Optional cleanup of the uncompressed dir +Remove-Item $outDir -Recurse -Force -ErrorAction SilentlyContinue diff --git a/scripts/powershell/pin-rdp-to-taskbar.ps1 b/scripts/powershell/pin-rdp-to-taskbar.ps1 new file mode 100644 index 0000000..a4f1aa8 --- /dev/null +++ b/scripts/powershell/pin-rdp-to-taskbar.ps1 @@ -0,0 +1,72 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Continue' + +Write-Host "[*] Pin RDP connection to taskbar" + +# File paths +$rdpFile = "C:\Users\User\Desktop\Remote-Desktop.rdp" +$shortcutPath = "C:\Users\User\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Remote Desktop.lnk" + +# Check if RDP file exists +if (-not (Test-Path $rdpFile)) { + Write-Host "[!] RDP file not found: $rdpFile" + exit 1 +} + +Write-Host "[*] Creating Start Menu shortcut" + +# Create WScript.Shell object +$WshShell = New-Object -comObject WScript.Shell + +# Create shortcut +$Shortcut = $WshShell.CreateShortcut($shortcutPath) +$Shortcut.TargetPath = "mstsc.exe" +$Shortcut.Arguments = "`"$rdpFile`"" +$Shortcut.WorkingDirectory = "C:\Windows\System32" +$Shortcut.IconLocation = "mstsc.exe,0" +$Shortcut.Description = "Remote Desktop Connection" +$Shortcut.WindowStyle = 1 +$Shortcut.Save() + +Write-Host " Shortcut created: $shortcutPath" + +# Pin to taskbar method +Write-Host "[*] Attempting to pin to taskbar" + +try { + # Method 1: Try using verb + $shell = New-Object -ComObject Shell.Application + $folder = $shell.Namespace((Split-Path $shortcutPath)) + $item = $folder.ParseName((Split-Path $shortcutPath -Leaf)) + + $verbs = $item.Verbs() + $pinVerb = $verbs | Where-Object { $_.Name -match "Pin to taskbar|taskbar" } + + if ($pinVerb) { + Write-Host " Found pin verb: $($pinVerb.Name)" + $pinVerb.DoIt() + Write-Host "[OK] Successfully pinned to taskbar" + } else { + Write-Host "[!] Pin to taskbar verb not found" + Write-Host "Manual steps:" + Write-Host "1. Open Start Menu" + Write-Host "2. Find 'Remote Desktop' shortcut" + Write-Host "3. Right-click > Pin to taskbar" + } + +} catch { + Write-Host "[!] Auto-pin failed: $($_.Exception.Message)" + Write-Host "Please pin manually from Start Menu" +} + +# Optionally remove desktop RDP file +Write-Host "[*] Cleaning up desktop RDP file" +try { + Remove-Item $rdpFile -Force + Write-Host " Desktop RDP file removed" +} catch { + Write-Host " Desktop RDP file kept" +} + +Write-Host "" +Write-Host "[OK] Setup complete! Check Start Menu for Remote Desktop shortcut" \ No newline at end of file diff --git a/scripts/powershell/recon.ps1 b/scripts/powershell/recon.ps1 new file mode 100644 index 0000000..a1d394d --- /dev/null +++ b/scripts/powershell/recon.ps1 @@ -0,0 +1,91 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'SilentlyContinue' + +function Section($title) { + Write-Host '' + Write-Host ('=' * 6) $title ('=' * 6) -ForegroundColor Cyan +} + +Section 'Computer' +Get-ComputerInfo -Property CsName,CsDomain,CsManufacturer,CsModel, + WindowsProductName,OsVersion,OsBuildNumber,OsArchitecture,OsInstallDate, + CsNumberOfProcessors,CsNumberOfLogicalProcessors,OsTotalVisibleMemorySize, + CsUserName,TimeZone | Format-List + +Section 'Boot Time' +(Get-CimInstance Win32_OperatingSystem).LastBootUpTime + +Section 'Local Users' +Get-LocalUser | Select-Object Name,Enabled,LastLogon,Description | Format-Table -AutoSize + +Section 'Administrators Group' +Get-LocalGroupMember -Group Administrators | + Select-Object Name,ObjectClass,PrincipalSource | Format-Table -AutoSize + +Section 'Disks' +Get-Volume | Where-Object DriveLetter | Select-Object DriveLetter,FileSystemLabel, + @{n='Size(GB)';e={[math]::Round($_.Size / 1GB, 1)}}, + @{n='Free(GB)';e={[math]::Round($_.SizeRemaining / 1GB, 1)}} | + Format-Table -AutoSize + +Section 'IPv4 Addresses' +Get-NetIPAddress -AddressFamily IPv4 | + Where-Object { $_.InterfaceAlias -notmatch 'Loopback' } | + Select-Object InterfaceAlias,IPAddress,PrefixLength | Format-Table -AutoSize + +Section 'Listening TCP Ports' +Get-NetTCPConnection -State Listen | + Select-Object LocalAddress,LocalPort,OwningProcess | + Sort-Object LocalPort -Unique | Format-Table -AutoSize + +Section 'Firewall Profiles' +Get-NetFirewallProfile | + Select-Object Name,Enabled,DefaultInboundAction,DefaultOutboundAction | + Format-Table -AutoSize + +Section 'SMB Shares' +Get-SmbShare | Select-Object Name,Path,Description | Format-Table -AutoSize + +Section 'Defender' +Get-MpComputerStatus | + Select-Object AMRunningMode,AntivirusEnabled,RealTimeProtectionEnabled, + AMEngineVersion,AntivirusSignatureLastUpdated | Format-List + +Section 'Installed Apps (non-Microsoft)' +$uninstallPaths = @( + 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*', + 'HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' +) +Get-ItemProperty $uninstallPaths | + Where-Object { $_.DisplayName -and $_.Publisher -notmatch 'Microsoft' } | + Select-Object DisplayName,DisplayVersion,Publisher,InstallDate | + Sort-Object DisplayName | Format-Table -AutoSize + +Section 'Startup Commands' +Get-CimInstance Win32_StartupCommand | + Select-Object Name,Command,Location,User | Format-Table -AutoSize + +Section 'Scheduled Tasks (non-Microsoft, enabled)' +Get-ScheduledTask | + Where-Object { $_.State -ne 'Disabled' -and $_.TaskPath -notlike '\Microsoft\*' } | + Select-Object TaskPath,TaskName,State | Format-Table -AutoSize + +Section 'SSH Services' +Get-Service sshd,ssh-agent | Format-Table Name,Status,StartType -AutoSize + +Section 'Recent Logons (Security 4624, last 48h)' +Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4624; StartTime=(Get-Date).AddHours(-48)} -MaxEvents 50 | + ForEach-Object { + $xml = [xml]$_.ToXml() + $d = @{} + $xml.Event.EventData.Data | ForEach-Object { $d[$_.Name] = $_.'#text' } + [pscustomobject]@{ + Time = $_.TimeCreated + LogonType = $d['LogonType'] + User = "$($d['TargetDomainName'])\$($d['TargetUserName'])" + IP = $d['IpAddress'] + Process = $d['ProcessName'] + } + } | + Where-Object { $_.User -notmatch 'SYSTEM|ANONYMOUS|LOCAL SERVICE|NETWORK SERVICE|DWM-|UMFD-|\$$' } | + Format-Table -AutoSize diff --git a/scripts/powershell/remove-apps.ps1 b/scripts/powershell/remove-apps.ps1 new file mode 100644 index 0000000..1c14cf8 --- /dev/null +++ b/scripts/powershell/remove-apps.ps1 @@ -0,0 +1,102 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Continue' + +$configPath = Join-Path $PSScriptRoot 'remove-apps-config.json' +if (-not (Test-Path $configPath)) { throw "Config not found: $configPath" } +$cfg = Get-Content $configPath -Raw -Encoding UTF8 | ConvertFrom-Json + +$results = @() + +function Record([string]$name, [string]$result, [string]$detail='') { + $script:results += [pscustomobject]@{ Name = $name; Result = $result; Detail = $detail } +} + +Write-Host "===== Removing Appx packages =====" +foreach ($name in @($cfg.RemoveAppx)) { + $pkgs = @(Get-AppxPackage -AllUsers -Name $name -ErrorAction SilentlyContinue) + $prov = @(Get-AppxProvisionedPackage -Online -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -eq $name }) + + if ($pkgs.Count -eq 0 -and $prov.Count -eq 0) { + Write-Host "[=] $name : not installed" + Record $name 'not-installed' + continue + } + + foreach ($p in $pkgs) { + try { + Write-Host "[*] Remove-AppxPackage $($p.PackageFullName)" + Remove-AppxPackage -AllUsers -Package $p.PackageFullName -ErrorAction Stop + } catch { + # 0x80073D19 = NonRemovable system package. Worth noting but often unavoidable. + if ($_.Exception.Message -match '0x80073D19|NonRemovable') { + Write-Host "[!] $name marked NonRemovable by Windows - skipping" + Record $name 'nonremovable' $_.Exception.Message + } else { + Write-Host "[!] $name install removal failed: $($_.Exception.Message)" + Record $name 'failed-install' $_.Exception.Message + } + } + } + foreach ($p in $prov) { + try { + Remove-AppxProvisionedPackage -Online -PackageName $p.PackageName -ErrorAction Stop | Out-Null + Write-Host "[*] Remove-AppxProvisionedPackage $($p.PackageName)" + } catch { + # Provisioned removal often fails silently when package was already deprovisioned by other tools. + # Only worth logging, not flagging as a hard failure. + } + } + + # Verify post-state + $after = @(Get-AppxPackage -AllUsers -Name $name -ErrorAction SilentlyContinue) + if ($after.Count -eq 0) { + Record $name 'removed' + } elseif (($results | Where-Object Name -eq $name | Select-Object -Last 1).Result -ne 'nonremovable') { + Record $name 'still-present' "after removal attempt: $(($after | Select -Expand PackageFullName) -join ',')" + } +} + +Write-Host "" +Write-Host "===== Office (M365) removal =====" +if ($cfg.RemoveOffice) { + $c2rClient = 'C:\Program Files\Common Files\microsoft shared\ClickToRun\OfficeClickToRun.exe' + if (Test-Path $c2rClient) { + Write-Host "[*] Click-to-Run detected - running silent uninstall" + $p = Start-Process -FilePath $c2rClient -ArgumentList 'scenario=uninstall','sourcetype=None','DisplayLevel=False' -Wait -PassThru + Write-Host "[=] OfficeClickToRun exited $($p.ExitCode)" + } else { + Write-Host "[=] No Click-to-Run Office present" + } + + $anyOfficeAppx = $false + foreach ($pat in @('Microsoft.MicrosoftOfficeHub','Microsoft.Office.*','Microsoft.OutlookForWindows')) { + $hit = @(Get-AppxPackage -AllUsers -Name $pat -ErrorAction SilentlyContinue) + foreach ($p in $hit) { + $anyOfficeAppx = $true + try { + Write-Host "[*] Remove-AppxPackage $($p.PackageFullName)" + Remove-AppxPackage -AllUsers -Package $p.PackageFullName -ErrorAction Stop + Record $p.Name 'removed' + } catch { + Write-Host "[!] $($p.Name) failed: $($_.Exception.Message)" + Record $p.Name 'failed-install' $_.Exception.Message + } + } + } + if ((-not (Test-Path $c2rClient)) -and (-not $anyOfficeAppx)) { + Write-Host "[=] No Office / M365 traces found - nothing to do" + } +} + +Write-Host "" +Write-Host "===== Summary =====" +$byResult = $results | Group-Object Result | Sort-Object Name +foreach ($g in $byResult) { + Write-Host ("{0,-15}: {1}" -f $g.Name, $g.Count) +} +$problem = $results | Where-Object { $_.Result -in 'failed-install','still-present' } +if ($problem) { + Write-Host "" + Write-Host "Needs attention:" + $problem | ForEach-Object { Write-Host " - $($_.Name) [$($_.Result)] $($_.Detail)" } +} diff --git a/scripts/powershell/remove-windows-ai.ps1 b/scripts/powershell/remove-windows-ai.ps1 new file mode 100644 index 0000000..7593611 --- /dev/null +++ b/scripts/powershell/remove-windows-ai.ps1 @@ -0,0 +1,82 @@ +param( + [ValidateSet('Apply','Revert')] + [string]$Mode = 'Apply', + [switch]$SkipDownload # use existing C:\Users\Admin\RemoveWindowsAi.ps1 instead of re-downloading +) + +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Stop' + +# 1. Must be Windows PowerShell 5.1, NOT pwsh 7+ +if ($PSVersionTable.PSVersion.Major -ge 7) { + throw "This wrapper must run under Windows PowerShell 5.1 (powershell.exe), not pwsh $($PSVersionTable.PSVersion). Relaunch explicitly with powershell.exe." +} + +# 2. Must have Administrator token +$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +if (-not $isAdmin) { + throw "Current session is NOT running as Administrator. RemoveWindowsAi.ps1 requires admin and its self-elevation (UAC RunAs) will fail over SSH. Ensure the SSH user has full admin token." +} + +# 3. Fetch upstream script +$workDir = Join-Path $env:TEMP 'RemoveWindowsAI' +$scriptPath = Join-Path $workDir 'RemoveWindowsAi.ps1' +$logPath = Join-Path $workDir ("run-$Mode-$(Get-Date -Format 'yyyyMMdd-HHmmss').log") +New-Item -ItemType Directory -Force -Path $workDir | Out-Null + +if (-not $SkipDownload) { + $url = 'https://raw.githubusercontent.com/zoicware/RemoveWindowsAI/main/RemoveWindowsAi.ps1' + Write-Host "[*] Downloading $url" + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + Invoke-WebRequest -Uri $url -OutFile $scriptPath -UseBasicParsing + if ((Get-Item $scriptPath).Length -lt 100KB) { + throw "Downloaded script seems too small ($((Get-Item $scriptPath).Length) bytes)" + } + # PowerShell 5.1 reads files without BOM using the system ANSI code page (CP950 on zh-TW), + # which mangles non-ASCII content. Force UTF-8 by prepending a BOM if not already present. + $bytes = [IO.File]::ReadAllBytes($scriptPath) + $hasBom = $bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF + if (-not $hasBom) { + $bom = [byte[]](0xEF, 0xBB, 0xBF) + [IO.File]::WriteAllBytes($scriptPath, $bom + $bytes) + Write-Host "[*] Added UTF-8 BOM" + } + Write-Host "[*] Saved -> $scriptPath ($((Get-Item $scriptPath).Length) bytes)" +} elseif (-not (Test-Path $scriptPath)) { + throw "-SkipDownload set but $scriptPath does not exist" +} + +# 4. Build args based on mode +$psArgs = @('-NoProfile','-ExecutionPolicy','Bypass','-File', $scriptPath, '-nonInteractive', '-EnableLogging') +if ($Mode -eq 'Apply') { + $psArgs += @('-AllOptions','-backupMode') + Write-Host "[*] Mode=Apply: -AllOptions -backupMode (backup saved for later revert)" +} else { + $psArgs += @('-AllOptions','-revertMode') + Write-Host "[*] Mode=Revert: -AllOptions -revertMode (requires prior backup)" +} + +# 5. Execute and tee output to log +Write-Host "[*] Executing: powershell.exe $($psArgs -join ' ')" +Write-Host "[*] Log -> $logPath" +Write-Host ('-' * 60) + +$proc = Start-Process -FilePath 'powershell.exe' -ArgumentList $psArgs ` + -NoNewWindow -Wait -PassThru ` + -RedirectStandardOutput $logPath -RedirectStandardError "$logPath.err" + +Write-Host ('-' * 60) +Get-Content $logPath -ErrorAction SilentlyContinue | Select-Object -Last 80 +if (Test-Path "$logPath.err") { + $errs = Get-Content "$logPath.err" + if ($errs) { + Write-Host "--- stderr ---" + $errs + } +} + +Write-Host "" +Write-Host "[=] Exit code: $($proc.ExitCode)" +Write-Host "[=] Full log: $logPath" +Write-Host "[!] A reboot is recommended after this operation." +exit $proc.ExitCode diff --git a/scripts/powershell/rename-hide-accounts.ps1 b/scripts/powershell/rename-hide-accounts.ps1 new file mode 100644 index 0000000..b8423d9 --- /dev/null +++ b/scripts/powershell/rename-hide-accounts.ps1 @@ -0,0 +1,51 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Stop' + +# --- 1. Rename local account 'user' -> 'User' --- +$src = 'user' +$dst = 'User' + +$u = Get-LocalUser -Name $src -ErrorAction SilentlyContinue +if ($u) { + if ($u.Name -ceq $dst) { + Write-Host "[=] Account is already '$dst'" + } else { + Write-Host "[*] Renaming local account '$($u.Name)' -> '$dst'" + Rename-LocalUser -Name $src -NewName $dst + Write-Host "[OK] Renamed" + } +} else { + $already = Get-LocalUser -Name $dst -ErrorAction SilentlyContinue + if ($already) { + Write-Host "[=] Account '$dst' already exists (no '$src' found). Nothing to rename." + } else { + throw "Neither '$src' nor '$dst' exists as local user" + } +} + +# --- 2. Hide 'Admin' from sign-in screen --- +$listPath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList' +if (-not (Test-Path $listPath)) { + New-Item -Path $listPath -Force | Out-Null + Write-Host "[*] Created $listPath" +} +New-ItemProperty -Path $listPath -Name 'Admin' -Value 0 -PropertyType DWord -Force | Out-Null +Write-Host "[OK] Hidden 'Admin' from sign-in screen (SpecialAccounts\UserList\Admin = 0)" + +Write-Host "" +Write-Host "=== Post-state ===" +Get-LocalUser | Select-Object Name,Enabled,LastLogon | Format-Table -AutoSize + +$lst = Get-ItemProperty -Path $listPath -ErrorAction SilentlyContinue +if ($lst) { + Write-Host "UserList values:" + $lst.PSObject.Properties | Where-Object { $_.Name -notmatch '^PS' } | ForEach-Object { + Write-Host (" {0} = {1}" -f $_.Name, $_.Value) + } +} + +Write-Host "" +Write-Host "Notes:" +Write-Host " - Account SIDs don't change. C:\Users\user profile folder stays with lowercase name" +Write-Host " (NTFS is case-insensitive so all apps keep working)." +Write-Host " - Admin can still sign in: click 'Other user' on the lock screen and type 'Admin'." diff --git a/scripts/powershell/set-user-desktop-icons.ps1 b/scripts/powershell/set-user-desktop-icons.ps1 new file mode 100644 index 0000000..d4f331b --- /dev/null +++ b/scripts/powershell/set-user-desktop-icons.ps1 @@ -0,0 +1,97 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Continue' + +Write-Host "[*] Setting desktop icons for User account" + +# Icon GUIDs +$computerGUID = "{20D04FE0-3AEA-1069-A2D8-08002B30309D}" +$controlPanelGUID = "{5399E694-6CE5-4D6C-8FCE-1D8870FDCBA0}" +$recycleGUID = "{645FF040-5081-101B-9F08-00AA002F954E}" + +# Get User SID +try { + $user = New-Object System.Security.Principal.NTAccount("User") + $userSID = $user.Translate([System.Security.Principal.SecurityIdentifier]).Value + Write-Host "[*] User SID: $userSID" +} catch { + Write-Host "[!] Could not get user SID, trying alternative method" + + # Try to get SID from registry + $userProfiles = Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" + foreach ($profile in $userProfiles) { + $profileData = Get-ItemProperty -Path $profile.PSPath -ErrorAction SilentlyContinue + if ($profileData.ProfileImagePath -like "*\User") { + $userSID = $profile.PSChildName + Write-Host "[*] Found User SID: $userSID" + break + } + } +} + +if (-not $userSID) { + Write-Host "[!] Could not determine User SID" -ForegroundColor Red + exit 1 +} + +# Check if user hive is loaded (user is logged in) +$userHivePath = "HKU:\$userSID" + +if (Test-Path $userHivePath) { + Write-Host "[*] User is currently logged in, modifying active hive" + + $desktopIconsPath = "$userHivePath\Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel" + $classicIconsPath = "$userHivePath\Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\ClassicStartMenu" + + # Ensure registry paths exist + foreach ($path in @($desktopIconsPath, $classicIconsPath)) { + if (-not (Test-Path $path)) { + New-Item -Path $path -Force | Out-Null + Write-Host " Created registry path" + } + } + + # Enable desktop icons + $icons = @{ + "Computer" = $computerGUID + "Control Panel" = $controlPanelGUID + "Recycle Bin" = $recycleGUID + } + + foreach ($iconName in $icons.Keys) { + $guid = $icons[$iconName] + Set-ItemProperty -Path $desktopIconsPath -Name $guid -Value 0 -Type DWord + Set-ItemProperty -Path $classicIconsPath -Name $guid -Value 0 -Type DWord + Write-Host " Enabled: $iconName" + } + + Write-Host "[*] Refreshing desktop for logged-in user" + + # Create a script to run as the user to refresh the desktop + $refreshScript = @" +Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public class Win32 { [DllImport("user32.dll")] public static extern bool InvalidateRect(IntPtr hWnd, IntPtr lpRect, bool bErase); [DllImport("user32.dll")] public static extern IntPtr GetDesktopWindow(); }' +[Win32]::InvalidateRect([Win32]::GetDesktopWindow(), [IntPtr]::Zero, $true) +"@ + + try { + # Try to refresh the desktop + Invoke-Expression $refreshScript + Write-Host " Desktop refresh attempted" + } catch { + Write-Host " Could not refresh desktop automatically" + } + + Write-Host "" + Write-Host "[OK] Desktop icons configured for active User session" + Write-Host "Icons should appear shortly. If not visible:" + Write-Host "1. Right-click desktop > Refresh" + Write-Host "2. Log out and log back in as User" + +} else { + Write-Host "[*] User is not currently logged in" + Write-Host "[!] Cannot modify registry while user is logged out" + Write-Host "" + Write-Host "Please:" + Write-Host "1. Log in as User" + Write-Host "2. Run this script again" + Write-Host "3. Or reboot and log in as User, then run the script" +} \ No newline at end of file diff --git a/scripts/powershell/setup-network-drive.ps1 b/scripts/powershell/setup-network-drive.ps1 new file mode 100644 index 0000000..d823bcd --- /dev/null +++ b/scripts/powershell/setup-network-drive.ps1 @@ -0,0 +1,138 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Continue' + +Write-Host "[*] Setting up network drive R: and removing desktop shortcut" + +# File paths +$shortcutPath = "C:\Users\User\Desktop\Network Share (RD).lnk" +$networkPath = "\\192.168.88.231\rd" +$driveLetter = "R:" + +Write-Host "[*] Step 1: Removing desktop shortcut" + +if (Test-Path $shortcutPath) { + try { + Remove-Item $shortcutPath -Force + Write-Host "[OK] Desktop shortcut removed: Network Share (RD).lnk" + } catch { + Write-Host "[!] Failed to remove shortcut: $($_.Exception.Message)" + } +} else { + Write-Host " Desktop shortcut not found (already removed)" +} + +Write-Host "[*] Step 2: Checking current network drives" + +# Check existing drives +$existingDrives = Get-WmiObject -Class Win32_LogicalDisk | Select-Object DeviceID, DriveType, ProviderName +$networkDrives = $existingDrives | Where-Object { $_.DriveType -eq 4 } + +if ($networkDrives) { + Write-Host " Current network drives:" + foreach ($drive in $networkDrives) { + Write-Host " - $($drive.DeviceID) -> $($drive.ProviderName)" + } +} else { + Write-Host " No network drives currently mapped" +} + +Write-Host "[*] Step 3: Mapping network drive R:" + +# Check if R: is already in use +$rDriveExists = $existingDrives | Where-Object { $_.DeviceID -eq $driveLetter } +if ($rDriveExists) { + Write-Host " R: drive already exists" + if ($rDriveExists.ProviderName -eq $networkPath) { + Write-Host "[OK] R: is already mapped to $networkPath" + exit 0 + } else { + Write-Host " R: is mapped to: $($rDriveExists.ProviderName)" + Write-Host " Disconnecting existing mapping..." + try { + & net use $driveLetter /delete /yes + Write-Host " Existing R: mapping removed" + } catch { + Write-Host "[!] Failed to disconnect R: drive" + } + } +} + +Write-Host " Mapping $networkPath to $driveLetter" + +# Test network connectivity first +Write-Host "[*] Step 4: Testing network connectivity" +try { + $testConnection = Test-NetConnection -ComputerName "192.168.88.231" -Port 445 -WarningAction SilentlyContinue + if ($testConnection.TcpTestSucceeded) { + Write-Host "[OK] Network connection to 192.168.88.231:445 successful" + } else { + Write-Host "[!] Warning: Cannot reach 192.168.88.231:445" + } +} catch { + Write-Host "[!] Network test failed: $($_.Exception.Message)" +} + +# Map the network drive +Write-Host "[*] Step 5: Creating network drive mapping" + +try { + # Use net use command for mapping + $result = & net use $driveLetter $networkPath 2>&1 + + if ($LASTEXITCODE -eq 0) { + Write-Host "[OK] Network drive mapped successfully!" + Write-Host " $driveLetter -> $networkPath" + + # Verify the mapping + Start-Sleep -Seconds 2 + $verification = Get-WmiObject -Class Win32_LogicalDisk | Where-Object { $_.DeviceID -eq $driveLetter } + if ($verification) { + Write-Host "[OK] Drive mapping verified" + Write-Host " Drive type: $($verification.DriveType) (4 = Network)" + Write-Host " Provider: $($verification.ProviderName)" + } + + } else { + Write-Host "[!] Network drive mapping failed" + Write-Host " Error output: $result" + + # Provide troubleshooting info + Write-Host "" + Write-Host "Possible solutions:" + Write-Host "1. Run as Administrator: net use R: \\192.168.88.231\rd" + Write-Host "2. With credentials: net use R: \\192.168.88.231\rd /user:username" + Write-Host "3. Check if the share exists and is accessible" + Write-Host "4. Verify network connectivity to 192.168.88.231" + } + +} catch { + Write-Host "[!] Exception during mapping: $($_.Exception.Message)" +} + +Write-Host "" +Write-Host "[*] Final status check" + +# Final verification +$finalDrives = Get-WmiObject -Class Win32_LogicalDisk | Where-Object { $_.DriveType -eq 4 } +if ($finalDrives) { + Write-Host "Current network drives:" + foreach ($drive in $finalDrives) { + if ($drive.DeviceID -eq $driveLetter) { + Write-Host "[OK] $($drive.DeviceID) -> $($drive.ProviderName) (SUCCESS)" + } else { + Write-Host " $($drive.DeviceID) -> $($drive.ProviderName)" + } + } +} else { + Write-Host "No network drives mapped" +} + +Write-Host "" +Write-Host "Setup complete!" +Write-Host "- Desktop shortcut removed" +Write-Host "- Network drive R: mapping attempted" +Write-Host "" +Write-Host "Access the network share via:" +Write-Host "1. File Explorer > This PC > R: drive" +Write-Host "2. Address bar: R:\" +Write-Host "3. Run dialog: R:\" \ No newline at end of file diff --git a/scripts/powershell/setup-network-share.ps1 b/scripts/powershell/setup-network-share.ps1 new file mode 100644 index 0000000..6cc61db --- /dev/null +++ b/scripts/powershell/setup-network-share.ps1 @@ -0,0 +1,102 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Continue' + +Write-Host "[*] Setting up network share access" + +# Network share path +$sharePath = "\\192.168.88.231\rd" +$shortcutPath = "C:\Users\User\Desktop\Network Share (RD).lnk" + +Write-Host "[*] Testing connection to network share" +Write-Host " Target: $sharePath" + +# Test network connectivity +try { + $testConnection = Test-NetConnection -ComputerName "192.168.88.231" -Port 445 -WarningAction SilentlyContinue + if ($testConnection.TcpTestSucceeded) { + Write-Host "[OK] Network connection successful (Port 445 accessible)" + } else { + Write-Host "[!] Warning: Port 445 not accessible - SMB might be blocked" + } +} catch { + Write-Host "[!] Network test failed: $($_.Exception.Message)" +} + +Write-Host "[*] Creating desktop shortcut to network share" + +# Create shortcut using WScript.Shell +try { + $WshShell = New-Object -comObject WScript.Shell + $Shortcut = $WshShell.CreateShortcut($shortcutPath) + $Shortcut.TargetPath = $sharePath + $Shortcut.WorkingDirectory = $sharePath + $Shortcut.IconLocation = "shell32.dll,4" # Folder icon + $Shortcut.Description = "Network Share - RD" + $Shortcut.Save() + + Write-Host "[OK] Desktop shortcut created: Network Share (RD).lnk" +} catch { + Write-Host "[!] Failed to create shortcut: $($_.Exception.Message)" +} + +Write-Host "[*] Testing share access" + +# Test if we can access the share +try { + if (Test-Path $sharePath) { + Write-Host "[OK] Network share is accessible" + + # Try to list contents + $items = Get-ChildItem $sharePath -ErrorAction SilentlyContinue | Select-Object -First 5 + if ($items) { + Write-Host " Found $($items.Count) items in share" + } else { + Write-Host " Share appears empty or no read permission" + } + } else { + Write-Host "[!] Network share not accessible" + Write-Host " Possible issues:" + Write-Host " - Share doesn't exist" + Write-Host " - No network access" + Write-Host " - Authentication required" + Write-Host " - SMB protocol blocked" + } +} catch { + Write-Host "[!] Error accessing share: $($_.Exception.Message)" +} + +Write-Host "[*] Checking if drive mapping is needed" + +# Check if we should map as network drive +$availableDrives = Get-WmiObject -Class Win32_LogicalDisk | Where-Object {$_.DriveType -eq 3} | Select-Object -ExpandProperty DeviceID +$networkDrives = Get-WmiObject -Class Win32_LogicalDisk | Where-Object {$_.DriveType -eq 4} | Select-Object -ExpandProperty DeviceID + +Write-Host " Current network drives: $($networkDrives -join ', ')" + +# Suggest mapping to Z: drive if not already used +if ("Z:" -notin ($availableDrives + $networkDrives)) { + Write-Host "[*] Attempting to map network drive Z:" + try { + & net use Z: $sharePath + Write-Host "[OK] Network drive Z: mapped successfully" + } catch { + Write-Host "[!] Drive mapping failed - may require credentials" + Write-Host " Manual mapping: net use Z: $sharePath /user:username" + } +} else { + Write-Host " Z: drive already in use" +} + +Write-Host "" +Write-Host "[OK] Network share setup complete!" +Write-Host "" +Write-Host "Access methods:" +Write-Host "1. Double-click 'Network Share (RD).lnk' on desktop" +Write-Host "2. Type '$sharePath' in File Explorer address bar" +if ("Z:" -notin $availableDrives) { + Write-Host "3. Use Z: drive if mapping was successful" +} +Write-Host "" +Write-Host "If access is denied, you may need to:" +Write-Host "- Right-click shortcut > Run as administrator" +Write-Host "- Provide network credentials when prompted" \ No newline at end of file diff --git a/scripts/powershell/setup-rdp-with-credentials.ps1 b/scripts/powershell/setup-rdp-with-credentials.ps1 new file mode 100644 index 0000000..d538200 --- /dev/null +++ b/scripts/powershell/setup-rdp-with-credentials.ps1 @@ -0,0 +1,89 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Continue' + +Write-Host "[*] Setting up RDP connection with saved credentials" + +# RDP file path on desktop +$rdpFile = "C:\Users\User\Desktop\Remote-PC.rdp" + +# RDP file content with credentials +$rdpContent = @" +screen mode id:i:2 +use multimon:i:0 +desktopwidth:i:1920 +desktopheight:i:1080 +session bpp:i:32 +winposstr:s:0,3,0,0,800,600 +compression:i:1 +keyboardhook:i:2 +audiocapturemode:i:0 +videoplaybackmode:i:1 +connection type:i:7 +networkautodetect:i:1 +bandwidthautodetect:i:1 +displayconnectionbar:i:1 +enableworkspacereconnect:i:0 +disable wallpaper:i:0 +allow font smoothing:i:0 +allow desktop composition:i:0 +disable full window drag:i:1 +disable menu anims:i:1 +disable themes:i:0 +disable cursor setting:i:0 +bitmapcachepersistenable:i:1 +full address:s:192.168.88.21 +audiomode:i:0 +redirectprinters:i:1 +redirectcomports:i:0 +redirectsmartcards:i:1 +redirectclipboard:i:1 +redirectposdevices:i:0 +autoreconnection enabled:i:1 +authentication level:i:2 +prompt for credentials:i:0 +negotiate security layer:i:1 +remoteapplicationmode:i:0 +alternate shell:s: +shell working directory:s: +gatewayhostname:s: +gatewayusagemethod:i:4 +gatewaycredentialssource:i:4 +gatewayprofileusagemethod:i:0 +promptcredentialonce:i:0 +gatewaybrokeringtype:i:0 +use redirection server name:i:0 +rdgiskdcproxy:i:0 +kdcproxyname:s: +drivestoredirect:s: +username:s:ChiaYing +domain:s: +"@ + +# Write RDP file +$rdpContent | Out-File -FilePath $rdpFile -Encoding UTF8 +Write-Host "[*] RDP file created: $rdpFile" + +# Add credential to Windows Credential Manager +Write-Host "[*] Adding credentials to Windows Credential Manager" + +try { + # Use cmdkey to add credential + & cmdkey /generic:"TERMSRV/192.168.88.21" /user:"ChiaYing" /pass:"!ChiaYing" + Write-Host "[OK] Credentials saved to Credential Manager" +} catch { + Write-Host "[!] Failed to save credentials: $($_.Exception.Message)" +} + +# Verify the RDP file was created +if (Test-Path $rdpFile) { + $fileInfo = Get-Item $rdpFile + Write-Host "[OK] RDP file ready: $($fileInfo.Name) ($($fileInfo.Length) bytes)" + Write-Host "" + Write-Host "Usage:" + Write-Host "1. Double-click 'Remote-PC.rdp' on desktop" + Write-Host "2. Connection should start automatically with saved credentials" + Write-Host "3. If prompted for password, enter: !ChiaYing" +} else { + Write-Host "[!] Failed to create RDP file" + exit 1 +} \ No newline at end of file diff --git a/scripts/powershell/setup-thunderbird.ps1 b/scripts/powershell/setup-thunderbird.ps1 new file mode 100644 index 0000000..aa04ed5 --- /dev/null +++ b/scripts/powershell/setup-thunderbird.ps1 @@ -0,0 +1,150 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Stop' + +$configPath = Join-Path $PSScriptRoot 'thunderbird-config.json' +if (-not (Test-Path $configPath)) { throw "Config not found: $configPath" } +$cfg = Get-Content $configPath -Raw -Encoding UTF8 | ConvertFrom-Json + +$socketMap = @{ 'none' = 0; 'STARTTLS' = 2; 'SSL' = 3 } +$authMap = @{ 'normal' = 3; 'encrypted' = 4; 'ntlm' = 8; 'gssapi' = 7; 'oauth2' = 10 } + +if (-not $socketMap.ContainsKey($cfg.Incoming.SocketType)) { + throw "Incoming.SocketType must be one of: $($socketMap.Keys -join ', ')" +} +if (-not $socketMap.ContainsKey($cfg.Outgoing.SocketType)) { + throw "Outgoing.SocketType must be one of: $($socketMap.Keys -join ', ')" +} +if (-not $authMap.ContainsKey($cfg.Incoming.AuthMethod)) { + throw "Incoming.AuthMethod must be one of: $($authMap.Keys -join ', ')" +} +if (-not $authMap.ContainsKey($cfg.Outgoing.AuthMethod)) { + throw "Outgoing.AuthMethod must be one of: $($authMap.Keys -join ', ')" +} + +$winUser = $cfg.WindowsUser +$appRoot = "C:\Users\$winUser\AppData\Roaming\Thunderbird" +$profilesDir = Join-Path $appRoot 'Profiles' +$profileName = $cfg.ProfileName +$profilePath = Join-Path $profilesDir $profileName +$iniPath = Join-Path $appRoot 'profiles.ini' + +if (-not (Test-Path "C:\Users\$winUser")) { + throw "Windows user '$winUser' not found (no C:\Users\$winUser folder)" +} + +# Stop any running Thunderbird so we don't fight it +Get-Process thunderbird -ErrorAction SilentlyContinue | Stop-Process -Force + +New-Item -ItemType Directory -Force -Path $profilePath | Out-Null + +$incSocket = $socketMap[$cfg.Incoming.SocketType] +$outSocket = $socketMap[$cfg.Outgoing.SocketType] +$incAuth = $authMap[$cfg.Incoming.AuthMethod] +$outAuth = $authMap[$cfg.Outgoing.AuthMethod] + +$prefs = @" +// Generated by setup-thunderbird.ps1 +user_pref("mail.shell.checkDefaultClient", false); +user_pref("mailnews.start_page.enabled", false); +user_pref("mail.accountmanager.accounts", "account1,account2"); +user_pref("mail.accountmanager.defaultaccount", "account1"); +user_pref("mail.accountmanager.localfoldersserver", "server2"); +user_pref("mail.account.account1.identities", "id1"); +user_pref("mail.account.account1.server", "server1"); +user_pref("mail.account.account2.server", "server2"); +user_pref("mail.server.server1.type", "$($cfg.Incoming.Type)"); +user_pref("mail.server.server1.hostname", "$($cfg.Incoming.Hostname)"); +user_pref("mail.server.server1.port", $($cfg.Incoming.Port)); +user_pref("mail.server.server1.socketType", $incSocket); +user_pref("mail.server.server1.userName", "$($cfg.Incoming.Username)"); +user_pref("mail.server.server1.authMethod", $incAuth); +user_pref("mail.server.server1.name", "$($cfg.Identity.Email)"); +user_pref("mail.server.server1.login_at_startup", true); +user_pref("mail.server.server1.check_new_mail", true); +user_pref("mail.server.server2.type", "none"); +user_pref("mail.server.server2.hostname", "Local Folders"); +user_pref("mail.server.server2.name", "Local Folders"); +user_pref("mail.server.server2.userName", "nobody"); +user_pref("mail.identity.id1.fullName", "$($cfg.Identity.FullName)"); +user_pref("mail.identity.id1.useremail", "$($cfg.Identity.Email)"); +user_pref("mail.identity.id1.smtpServer", "smtp1"); +user_pref("mail.identity.id1.valid", true); +user_pref("mail.smtpservers", "smtp1"); +user_pref("mail.smtp.defaultserver", "smtp1"); +user_pref("mail.smtpserver.smtp1.hostname", "$($cfg.Outgoing.Hostname)"); +user_pref("mail.smtpserver.smtp1.port", $($cfg.Outgoing.Port)); +user_pref("mail.smtpserver.smtp1.try_ssl", $outSocket); +user_pref("mail.smtpserver.smtp1.username", "$($cfg.Outgoing.Username)"); +user_pref("mail.smtpserver.smtp1.authMethod", $outAuth); +user_pref("mail.smtpserver.smtp1.description", "$($cfg.Outgoing.Hostname)"); +"@ + +$prefsPath = Join-Path $profilePath 'prefs.js' +Set-Content -Path $prefsPath -Value $prefs -Encoding UTF8 +Write-Host "[*] Wrote prefs.js -> $prefsPath" + +# profiles.ini: Thunderbird 72+ selects profile via [Install] section, not Default=1 on Profile. +# Merge: preserve existing Profile/Install entries, force our profile as default everywhere. +$profileRelPath = "Profiles/$profileName" + +if (Test-Path $iniPath) { + $iniRaw = Get-Content $iniPath -Raw + # Redirect any existing Install block to our profile + $iniRaw = [regex]::Replace($iniRaw, '(?m)^Default=Profiles/[^\r\n]+', "Default=$profileRelPath") + # Clear Default=1 from other Profile sections so only ours is default + $iniRaw = [regex]::Replace($iniRaw, '(?ms)(\[Profile\d+\][^\[]*?Name=(?!' + [regex]::Escape($profileName) + ')[^\r\n]+[^\[]*?)Default=1\s*', '$1') + + # If our Profile entry is missing, append it + if ($iniRaw -notmatch "(?m)^Name=$([regex]::Escape($profileName))\s*$") { + $nextIdx = 0 + while ($iniRaw -match "(?m)^\[Profile$nextIdx\]") { $nextIdx++ } + $append = @" + +[Profile$nextIdx] +Name=$profileName +IsRelative=1 +Path=$profileRelPath +Default=1 +"@ + $iniRaw = $iniRaw.TrimEnd() + "`r`n" + $append + "`r`n" + } + Set-Content -Path $iniPath -Value $iniRaw -Encoding ASCII + Write-Host "[*] Merged profiles.ini -> $iniPath" +} else { + $ini = @" +[General] +StartWithLastProfile=1 +Version=2 + +[Profile0] +Name=$profileName +IsRelative=1 +Path=$profileRelPath +Default=1 +"@ + Set-Content -Path $iniPath -Value $ini -Encoding ASCII + Write-Host "[*] Wrote profiles.ini -> $iniPath" +} + +# Also update installs.ini (Thunderbird 72+) if present +$installsIni = Join-Path $appRoot 'installs.ini' +if (Test-Path $installsIni) { + $raw = Get-Content $installsIni -Raw + $raw = [regex]::Replace($raw, '(?m)^Default=Profiles/[^\r\n]+', "Default=$profileRelPath") + Set-Content -Path $installsIni -Value $raw -Encoding ASCII + Write-Host "[*] Updated installs.ini -> $installsIni" +} + +# If we're running as a different user than the profile target, fix ownership so they can read/write +if ($winUser -ne $env:USERNAME) { + Write-Host "[*] Adjusting ACL for '$winUser' on $appRoot" + & icacls $appRoot /grant "${winUser}:(OI)(CI)F" /T /Q | Out-Null +} + +Write-Host "" +Write-Host "[OK] Thunderbird profile '$profileName' configured for Windows user '$winUser'" +Write-Host " Identity: $($cfg.Identity.FullName) <$($cfg.Identity.Email)>" +Write-Host " Incoming: $($cfg.Incoming.Type) $($cfg.Incoming.Hostname):$($cfg.Incoming.Port) ($($cfg.Incoming.SocketType))" +Write-Host " Outgoing: smtp $($cfg.Outgoing.Hostname):$($cfg.Outgoing.Port) ($($cfg.Outgoing.SocketType))" +Write-Host "" +Write-Host "Next step: log in as '$winUser', start Thunderbird; it will prompt for the password on first connection." diff --git a/scripts/powershell/verify-ai-removed.ps1 b/scripts/powershell/verify-ai-removed.ps1 new file mode 100644 index 0000000..65605af --- /dev/null +++ b/scripts/powershell/verify-ai-removed.ps1 @@ -0,0 +1,111 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'SilentlyContinue' + +$pass = 0; $fail = 0 +function Check($label, [bool]$ok, $detail='') { + if ($ok) { $script:pass++; $mark = '[OK] ' } else { $script:fail++; $mark = '[FAIL]' } + Write-Host ("{0} {1}" -f $mark, $label) + if ($detail) { Write-Host (" {0}" -f $detail) } +} + +Write-Host "===== Services (expect removed) =====" +# Only actual AI services that RemoveWindowsAI targets. +foreach ($s in @('WSAIFabricSvc','AarSvc')) { + $svc = Get-Service -Name $s -ErrorAction SilentlyContinue + $detail = if ($svc) { "still present (Status=$($svc.Status))" } else { "gone" } + Check "service '$s' removed" (-not $svc) $detail +} + +Write-Host "" +Write-Host "===== Appx packages (expect removed) =====" +foreach ($pat in @('Microsoft.Copilot','Microsoft.MicrosoftCopilot','Microsoft.Windows.Copilot','MicrosoftWindows.Client.AIX','MicrosoftWindows.Client.CoreAI')) { + $pkg = Get-AppxPackage -AllUsers -Name "$pat*" -ErrorAction SilentlyContinue + $detail = if ($pkg) { "still present: $(($pkg | Select -Expand Name) -join ',')" } else { "not installed" } + Check "Appx '$pat*' removed" (-not $pkg) $detail +} + +Write-Host "" +Write-Host "===== Recall =====" +$recall = Get-WindowsOptionalFeature -Online -FeatureName 'Recall' -ErrorAction SilentlyContinue +if ($recall) { + Check "Recall optional feature disabled" ($recall.State -eq 'Disabled') "state=$($recall.State)" +} else { + Check "Recall optional feature absent" $true "feature not found" +} +$rct = @(Get-ScheduledTask -TaskPath '\Microsoft\Windows\WindowsAI\*' -ErrorAction SilentlyContinue) +$detail = if ($rct.Count -gt 0) { "remaining: $(($rct | Select -Expand TaskName) -join ',')" } else { "none" } +Check "WindowsAI scheduled tasks removed" ($rct.Count -eq 0) $detail + +Write-Host "" +Write-Host "===== Policy registry =====" +# Copilot's turn-off flag is under HKCU (tool writes to current user hive). +# WindowsAI policy keys are machine-wide (HKLM). +$policies = @( + @{ Path = 'HKCU:\SOFTWARE\Policies\Microsoft\Windows\WindowsCopilot'; Name = 'TurnOffWindowsCopilot'; Expect = 1 }, + @{ Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI'; Name = 'DisableAIDataAnalysis'; Expect = 1 }, + @{ Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI'; Name = 'DisableClickToDo'; Expect = 1 }, + @{ Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI'; Name = 'AllowRecallEnablement'; Expect = 0 } +) +foreach ($p in $policies) { + $val = (Get-ItemProperty -Path $p.Path -Name $p.Name -ErrorAction SilentlyContinue).($p.Name) + $shortPath = $p.Path -replace '^HK..:\\SOFTWARE\\','' + Check "$shortPath : $($p.Name) == $($p.Expect)" ($val -eq $p.Expect) "actual=$val" +} + +Write-Host "" +Write-Host "===== Notepad Rewrite (per-user, populated on first Notepad launch) =====" +# Key only appears after a user opens Notepad. Fail only if any user hive has RewriteEnabled=1. +$bad = @() +Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue | ForEach-Object { + $np = "Registry::$($_.Name)\Software\Microsoft\Notepad" + $v = (Get-ItemProperty $np -Name RewriteEnabled -ErrorAction SilentlyContinue).RewriteEnabled + if ($v -eq 1) { $bad += "$($_.Name)=1" } +} +$d = if ($bad) { "enabled in: $($bad -join ',')" } else { "not enabled in any user hive" } +Check "Notepad Rewrite not enabled anywhere" (-not $bad) $d + +Write-Host "" +Write-Host "===== IntegratedServicesRegionPolicySet (Copilot entries) =====" +$jsonPath = 'C:\Windows\System32\IntegratedServicesRegionPolicySet.json' +if (Test-Path $jsonPath) { + $j = Get-Content $jsonPath -Raw | ConvertFrom-Json + $copilot = @($j.policies | Where-Object { ($_ | ConvertTo-Json -Depth 3) -match '[Cc]opilot' }) + $disabled = @($copilot | Where-Object { $_.defaultState -eq 'disabled' }) + $ok = ($copilot.Count -gt 0) -and ($disabled.Count -eq $copilot.Count) + Check "all Copilot policies disabled" $ok "disabled $($disabled.Count) of $($copilot.Count) Copilot entries" +} else { + Check "IntegratedServicesRegionPolicySet.json present" $false "file missing" +} + +Write-Host "" +Write-Host "===== Update cleanup safety net =====" +$uct = Get-ScheduledTask -TaskName '*RemoveAI*' -ErrorAction SilentlyContinue +$uctDetail = if ($uct) { "found: $($uct.TaskName), state=$($uct.State)" } else { "missing" } +Check "RemoveAI update-cleanup scheduled task exists" ([bool]$uct) $uctDetail + +Write-Host "" +Write-Host "===== Leftover AI SystemApps (expect gone) =====" +# Only check SystemApps dirs and WindowsApps — DLLs in System32 are protected and not +# expected to be deleted; the services/executables are what matter. +$paths = @( + 'C:\Windows\SystemApps\MicrosoftWindows.Client.CoPilot_cw5n1h2txyewy', + 'C:\Windows\SystemApps\MicrosoftWindows.Client.AIX_cw5n1h2txyewy' +) +foreach ($p in $paths) { + $hit = Get-Item -Path $p -ErrorAction SilentlyContinue + $d = if ($hit) { "still exists" } else { "gone" } + Check "path gone: $p" (-not $hit) $d +} +$wap = @(Get-ChildItem 'C:\Program Files\WindowsApps' -Filter 'Microsoft.Copilot_*' -ErrorAction SilentlyContinue) +$d = if ($wap.Count -gt 0) { "$($wap.Count) dir(s)" } else { "none" } +Check "WindowsApps Microsoft.Copilot_* gone" ($wap.Count -eq 0) $d + +Write-Host "" +Write-Host "===== Restore point =====" +$rps = Get-ComputerRestorePoint -ErrorAction SilentlyContinue | Where-Object { $_.Description -match 'RemoveWindowsAI' } +$rpDetail = if ($rps) { "found: $(($rps | Select -Expand Description) -join ',')" } else { "none" } +Check "RemoveWindowsAI restore point exists" ([bool]$rps) $rpDetail + +Write-Host "" +Write-Host "===== Summary =====" +Write-Host ("PASS: {0} FAIL: {1}" -f $pass, $fail) diff --git a/scripts/powershell/verify-debloat.ps1 b/scripts/powershell/verify-debloat.ps1 new file mode 100644 index 0000000..cdfb0e5 --- /dev/null +++ b/scripts/powershell/verify-debloat.ps1 @@ -0,0 +1,106 @@ +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'SilentlyContinue' + +$pass = 0; $fail = 0 +function Check($label, [bool]$ok, $detail='') { + if ($ok) { $script:pass++; $mark = '[OK] ' } else { $script:fail++; $mark = '[FAIL]' } + Write-Host ("{0} {1}" -f $mark, $label) + if ($detail) { Write-Host (" {0}" -f $detail) } +} + +$userHives = Get-ChildItem 'Registry::HKEY_USERS' | + Where-Object { $_.Name -match 'S-1-5-21-' -and $_.Name -notmatch '_Classes$' } | + ForEach-Object { $_.Name } + +function Get-UserRegValue { + param([string]$SubPath, [string]$Name) + $hits = @() + foreach ($h in $userHives) { + $p = "Registry::$h\$SubPath" + $v = (Get-ItemProperty -Path $p -Name $Name -ErrorAction SilentlyContinue).$Name + if ($null -ne $v) { $hits += [pscustomobject]@{ Hive=$h; Value=$v } } + } + return $hits +} + +Write-Host "===== Appx packages removed =====" +foreach ($name in @('Microsoft.GamingApp','Microsoft.XboxGameOverlay','Microsoft.XboxGamingOverlay','Microsoft.windowscommunicationsapps','Microsoft.People','Microsoft.OutlookForWindows','Microsoft.BingSearch','Microsoft.Copilot','Microsoft.StartExperiencesApp')) { + $pkg = Get-AppxPackage -AllUsers -Name $name -ErrorAction SilentlyContinue + $detail = if ($pkg) { "still present" } else { "gone" } + Check "Appx '$name' removed" (-not $pkg) $detail +} + +Write-Host "" +Write-Host "===== Telemetry (HKLM) =====" +$val = (Get-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\DataCollection' -Name AllowTelemetry).AllowTelemetry +Check "Policies\DataCollection\AllowTelemetry == 0" ($val -eq 0) "actual=$val" + +Write-Host "" +Write-Host "===== Widgets / News & Interests (HKLM) =====" +$val = (Get-ItemProperty 'HKLM:\Software\Policies\Microsoft\Dsh' -Name AllowNewsAndInterests).AllowNewsAndInterests +Check "Dsh\AllowNewsAndInterests == 0" ($val -eq 0) "actual=$val" + +Write-Host "" +Write-Host "===== GameDVR (HKLM policy + per-user) =====" +$val = (Get-ItemProperty 'HKLM:\Software\Policies\Microsoft\Windows\GameDVR' -Name AllowGameDVR).AllowGameDVR +Check "GameDVR policy AllowGameDVR == 0" ($val -eq 0) "actual=$val" +$userDVR = Get-UserRegValue -SubPath 'System\GameConfigStore' -Name 'GameDVR_Enabled' +$match = @($userDVR | Where-Object { $_.Value -eq 0 }) +Check "per-user GameDVR_Enabled == 0" ($match.Count -gt 0) "disabled in $($match.Count)/$($userDVR.Count) user hives" + +Write-Host "" +Write-Host "===== Update behavior =====" +$val = (Get-ItemProperty 'HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate\AU' -Name NoAutoRebootWithLoggedOnUsers).NoAutoRebootWithLoggedOnUsers +Check "WindowsUpdate\AU\NoAutoRebootWithLoggedOnUsers == 1" ($val -eq 1) "actual=$val" + +Write-Host "" +Write-Host "===== Edge ads / Settings ads =====" +foreach ($c in @( + @{ Path='HKLM:\Software\Policies\Microsoft\Edge'; Name='ShowRecommendationsEnabled'; Expect=0 }, + @{ Path='HKLM:\Software\Policies\Microsoft\Edge'; Name='HubsSidebarEnabled'; Expect=0 } +)) { + $val = (Get-ItemProperty -Path $c.Path -Name $c.Name -ErrorAction SilentlyContinue).($c.Name) + $short = $c.Path -replace '^HKLM:\\','' + Check "$short : $($c.Name) == $($c.Expect)" ($val -eq $c.Expect) "actual=$val" +} + +Write-Host "" +Write-Host "===== Bing / Search suggestions (per-user) =====" +foreach ($c in @( + @{ Sub='Software\Policies\Microsoft\Windows\Explorer'; Name='DisableSearchBoxSuggestions'; Expect=1; Desc='Bing web search blocked in Start/Search' }, + @{ Sub='Software\Microsoft\Windows\CurrentVersion\SearchSettings'; Name='IsDynamicSearchBoxEnabled'; Expect=0; Desc='Search highlights disabled' } +)) { + $hits = Get-UserRegValue -SubPath $c.Sub -Name $c.Name + $match = @($hits | Where-Object { $_.Value -eq $c.Expect }) + Check $c.Desc ($match.Count -gt 0) "$($match.Count) of $($hits.Count) user hives match" +} + +Write-Host "" +Write-Host "===== Spotlight (per-user) =====" +$hits = Get-UserRegValue -SubPath 'Software\Policies\Microsoft\Windows\CloudContent' -Name 'DisableSpotlightCollectionOnDesktop' +$match = @($hits | Where-Object { $_.Value -eq 1 }) +Check 'Desktop Spotlight disabled' ($match.Count -gt 0) "$($match.Count) of $($hits.Count) user hives match" + +Write-Host "" +Write-Host "===== Explorer / UI (per-user) =====" +$uiChecks = @( + @{ Sub='Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced'; Name='HideFileExt'; Expect=0; Desc='Show known file extensions' }, + @{ Sub='Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced'; Name='Hidden'; Expect=1; Desc='Show hidden items' }, + @{ Sub='Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced'; Name='TaskbarAl'; Expect=0; Desc='Taskbar aligned left' }, + @{ Sub='Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced'; Name='ShowTaskViewButton'; Expect=0; Desc='Taskview button hidden' }, + @{ Sub='Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced'; Name='LaunchTo'; Expect=1; Desc='Explorer opens to This PC' }, + @{ Sub='Software\Microsoft\Windows\CurrentVersion\Themes\Personalize'; Name='AppsUseLightTheme'; Expect=0; Desc='Dark mode for apps' }, + @{ Sub='Software\Microsoft\Windows\CurrentVersion\Themes\Personalize'; Name='SystemUsesLightTheme'; Expect=0; Desc='Dark mode for system' } +) +foreach ($r in $uiChecks) { + $hits = Get-UserRegValue -SubPath $r.Sub -Name $r.Name + $match = @($hits | Where-Object { $_.Value -eq $r.Expect }) + $ok = $match.Count -gt 0 + $detail = if ($hits.Count -eq 0) { "not set in any user hive" } + else { ($hits | ForEach-Object { "$(Split-Path $_.Hive -Leaf)=$($_.Value)" }) -join ', ' } + Check $r.Desc $ok $detail +} + +Write-Host "" +Write-Host "===== Summary =====" +Write-Host ("PASS: {0} FAIL: {1}" -f $pass, $fail) diff --git a/scripts/powershell/win11debloat.ps1 b/scripts/powershell/win11debloat.ps1 new file mode 100644 index 0000000..6401d31 --- /dev/null +++ b/scripts/powershell/win11debloat.ps1 @@ -0,0 +1,91 @@ +param( + [switch]$KeepWorkDir +) + +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$ErrorActionPreference = 'Stop' + +# 1. Must be Windows PowerShell 5.1 (Win11Debloat targets powershell.exe) +if ($PSVersionTable.PSVersion.Major -ge 7) { + throw "Run with powershell.exe 5.1, not pwsh $($PSVersionTable.PSVersion)" +} + +# 2. Must have Administrator token +$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +if (-not $isAdmin) { throw "Administrator token required" } + +# 3. Config must exist beside this script +$userConfigSrc = Join-Path $PSScriptRoot 'win11debloat-config.json' +if (-not (Test-Path $userConfigSrc)) { throw "Missing $userConfigSrc" } + +# 4. Download repo zip to a fresh work dir +$workDir = Join-Path $env:TEMP 'Win11Debloat-run' +if (Test-Path $workDir) { Remove-Item $workDir -Recurse -Force } +New-Item -ItemType Directory -Path $workDir | Out-Null + +$zipUrl = 'https://github.com/Raphire/Win11Debloat/archive/refs/heads/master.zip' +$zipPath = Join-Path $workDir 'repo.zip' +Write-Host "[*] Downloading $zipUrl" +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +Invoke-WebRequest -Uri $zipUrl -OutFile $zipPath -UseBasicParsing +if ((Get-Item $zipPath).Length -lt 100KB) { throw "Downloaded zip seems too small" } +Expand-Archive -Path $zipPath -DestinationPath $workDir -Force + +$repoRoot = Join-Path $workDir 'Win11Debloat-master' +if (-not (Test-Path (Join-Path $repoRoot 'Win11Debloat.ps1'))) { + throw "Win11Debloat.ps1 missing after extract" +} + +# 5. Stage our config alongside the repo and BOM-patch all .ps1 files +# (PowerShell 5.1 on zh-TW Windows reads non-BOM files as CP950 → breaks) +$userConfigDst = Join-Path $repoRoot 'my-config.json' +Copy-Item $userConfigSrc $userConfigDst -Force + +$psFiles = Get-ChildItem -Path $repoRoot -Recurse -Filter '*.ps1' +$added = 0 +foreach ($f in $psFiles) { + $bytes = [IO.File]::ReadAllBytes($f.FullName) + $hasBom = $bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF + if (-not $hasBom) { + $bom = [byte[]](0xEF, 0xBB, 0xBF) + [IO.File]::WriteAllBytes($f.FullName, $bom + $bytes) + $added++ + } +} +Write-Host "[*] BOM-patched $added of $($psFiles.Count) .ps1 files" + +# 6. Run Win11Debloat +$logPath = Join-Path $repoRoot ("run-$(Get-Date -Format 'yyyyMMdd-HHmmss').log") +$script = Join-Path $repoRoot 'Win11Debloat.ps1' + +Write-Host "[*] Running Win11Debloat -Silent -Config my-config.json" +Write-Host "[*] Log -> $logPath" +Write-Host ('-' * 60) + +Push-Location $repoRoot +try { + $proc = Start-Process -FilePath 'powershell.exe' -ArgumentList @( + '-NoProfile','-ExecutionPolicy','Bypass','-File', $script, + '-Silent','-Config', $userConfigDst, '-LogPath', $logPath, '-NoRestartExplorer' + ) -NoNewWindow -Wait -PassThru +} +finally { + Pop-Location +} + +Write-Host ('-' * 60) +if (Test-Path $logPath) { + Write-Host "--- log tail ---" + Get-Content $logPath | Select-Object -Last 100 +} + +Write-Host "" +Write-Host "[=] Exit code: $($proc.ExitCode)" +Write-Host "[=] Full log: $logPath" +Write-Host "[!] Reboot (or at least restart Explorer) to apply UI changes." + +if (-not $KeepWorkDir) { + Write-Host "[*] Cleaning up $workDir (use -KeepWorkDir to keep)" + Remove-Item $workDir -Recurse -Force -ErrorAction SilentlyContinue +} +exit $proc.ExitCode diff --git a/scripts/ssh/activate-windows.sh b/scripts/ssh/activate-windows.sh new file mode 100755 index 0000000..69589b2 --- /dev/null +++ b/scripts/ssh/activate-windows.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${HOST:-192.168.88.108}" +USER_NAME="${USER_NAME:-Admin}" +PASS="${PASS:-P@ssw0rd!}" + +HERE="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$HERE/../powershell/activate-windows.ps1" +LOG_DIR="$HERE/../../logs" +STAMP="$(date +%Y%m%d-%H%M%S)" +LOG="$LOG_DIR/activate-windows-${HOST}-${STAMP}.log" + +mkdir -p "$LOG_DIR" + +SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10) + +echo "[*] 上傳 Windows 啟用腳本到 $USER_NAME@$HOST" +sshpass -p "$PASS" scp "${SSH_OPTS[@]}" "$SCRIPT" \ + "$USER_NAME@$HOST:C:/Users/$USER_NAME/activate-windows.ps1" >/dev/null + +echo "[*] 執行 Windows 啟用程序,log -> $LOG" +sshpass -p "$PASS" ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" \ + "powershell -NoProfile -ExecutionPolicy Bypass -File C:/Users/$USER_NAME/activate-windows.ps1" \ + 2>&1 | tee "$LOG" + +echo "" +echo "[*] 完成。紀錄已儲存: $LOG" \ No newline at end of file diff --git a/scripts/ssh/apply-ui-to-user.sh b/scripts/ssh/apply-ui-to-user.sh new file mode 100755 index 0000000..bfb8e27 --- /dev/null +++ b/scripts/ssh/apply-ui-to-user.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${HOST:-192.168.88.108}" +USER_NAME="${USER_NAME:-Admin}" +PASS="${PASS:-P@ssw0rd!}" +TARGET_USER="${TARGET_USER:-user}" + +HERE="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$HERE/../powershell/apply-ui-to-user.ps1" +LOG_DIR="$HERE/../../logs" +STAMP="$(date +%Y%m%d-%H%M%S)" +LOG="$LOG_DIR/apply-ui-to-user-${HOST}-${TARGET_USER}-${STAMP}.log" + +mkdir -p "$LOG_DIR" + +SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10) + +echo "[*] Uploading apply-ui-to-user.ps1 to $USER_NAME@$HOST" +sshpass -p "$PASS" scp "${SSH_OPTS[@]}" "$SCRIPT" \ + "$USER_NAME@$HOST:C:/Users/$USER_NAME/apply-ui-to-user.ps1" >/dev/null + +echo "[*] Applying UI settings to '$TARGET_USER' on $HOST, log -> $LOG" +sshpass -p "$PASS" ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" \ + "powershell -NoProfile -ExecutionPolicy Bypass -File C:/Users/$USER_NAME/apply-ui-to-user.ps1 -TargetUser $TARGET_USER" \ + 2>&1 | tee "$LOG" + +echo "[*] Done. Saved: $LOG" diff --git a/scripts/ssh/check-winrm.sh b/scripts/ssh/check-winrm.sh new file mode 100755 index 0000000..1cfc1a2 --- /dev/null +++ b/scripts/ssh/check-winrm.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${HOST:-192.168.88.108}" +USER_NAME="${USER_NAME:-Admin}" +PASS="${PASS:-P@ssw0rd!}" + +HERE="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$HERE/../powershell/check-winrm.ps1" +LOG_DIR="$HERE/../../logs" +STAMP="$(date +%Y%m%d-%H%M%S)" +LOG="$LOG_DIR/check-winrm-${HOST}-${STAMP}.log" + +mkdir -p "$LOG_DIR" + +SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10) + +echo "[*] Checking WinRM status on $USER_NAME@$HOST" +echo "[*] Uploading WinRM check script..." + +sshpass -p "$PASS" scp "${SSH_OPTS[@]}" "$SCRIPT" "$USER_NAME@$HOST:C:/Users/$USER_NAME/check-winrm.ps1" >/dev/null + +echo "[*] Running WinRM configuration check, log -> $LOG" +echo "==================================================================" + +sshpass -p "$PASS" ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" \ + "powershell -NoProfile -ExecutionPolicy Bypass -File C:/Users/$USER_NAME/check-winrm.ps1" \ + 2>&1 | tee "$LOG" + +echo "==================================================================" +echo "[*] WinRM check completed. Log saved: $LOG" +echo "" +echo "Next steps based on results:" +echo " ✅ If WinRM is ready -> Create WinRM connection scripts" +echo " ⚠️ If partially ready -> Run enable-winrm.sh to fix configuration" +echo " ❌ If not configured -> Run enable-winrm.sh to set up WinRM" \ No newline at end of file diff --git a/scripts/ssh/configure-ime.sh b/scripts/ssh/configure-ime.sh new file mode 100755 index 0000000..27209ca --- /dev/null +++ b/scripts/ssh/configure-ime.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${HOST:-192.168.88.108}" +USER_NAME="${USER_NAME:-Admin}" +PASS="${PASS:-P@ssw0rd!}" +TARGET_USER="${TARGET_USER:-User}" + +HERE="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$HERE/../powershell/configure-ime.ps1" +LOG_DIR="$HERE/../../logs" +STAMP="$(date +%Y%m%d-%H%M%S)" +LOG="$LOG_DIR/configure-ime-${HOST}-${TARGET_USER}-${STAMP}.log" + +mkdir -p "$LOG_DIR" + +SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10) + +echo "[*] Uploading configure-ime.ps1 to $USER_NAME@$HOST" +sshpass -p "$PASS" scp "${SSH_OPTS[@]}" "$SCRIPT" \ + "$USER_NAME@$HOST:C:/Users/$USER_NAME/configure-ime.ps1" >/dev/null + +echo "[*] Configuring IME for '$TARGET_USER' on $HOST, log -> $LOG" +sshpass -p "$PASS" ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" \ + "powershell -NoProfile -ExecutionPolicy Bypass -File C:/Users/$USER_NAME/configure-ime.ps1 -TargetUser $TARGET_USER" \ + 2>&1 | tee "$LOG" + +echo "[*] Done. Saved: $LOG" diff --git a/scripts/ssh/debug-winrm-network.sh b/scripts/ssh/debug-winrm-network.sh new file mode 100755 index 0000000..f96eb5a --- /dev/null +++ b/scripts/ssh/debug-winrm-network.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${HOST:-pc-74269}" +USER_NAME="${USER_NAME:-Admin}" +PASS="${PASS:-P@ssw0rd!}" + +HERE="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$HERE/../powershell/debug-winrm-network.ps1" +LOG_DIR="$HERE/../../logs" +STAMP="$(date +%Y%m%d-%H%M%S)" +LOG="$LOG_DIR/debug-winrm-network-${HOST}-${STAMP}.log" + +mkdir -p "$LOG_DIR" + +SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10) + +echo "[*] Debugging WinRM network connectivity on $USER_NAME@$HOST" +echo "[*] Uploading diagnostic script..." + +sshpass -p "$PASS" scp "${SSH_OPTS[@]}" "$SCRIPT" "$USER_NAME@$HOST:C:/Users/$USER_NAME/debug-winrm-network.ps1" >/dev/null + +echo "[*] Running WinRM network diagnostics, log -> $LOG" +echo "==================================================================" + +sshpass -p "$PASS" ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" \ + "powershell -NoProfile -ExecutionPolicy Bypass -File C:/Users/$USER_NAME/debug-winrm-network.ps1" \ + 2>&1 | tee "$LOG" + +echo "==================================================================" +echo "[*] WinRM network diagnostics completed. Log saved: $LOG" \ No newline at end of file diff --git a/scripts/ssh/enable-winrm.sh b/scripts/ssh/enable-winrm.sh new file mode 100755 index 0000000..2920d83 --- /dev/null +++ b/scripts/ssh/enable-winrm.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${HOST:-192.168.88.108}" +USER_NAME="${USER_NAME:-Admin}" +PASS="${PASS:-P@ssw0rd!}" + +HERE="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$HERE/../powershell/enable-winrm.ps1" +LOG_DIR="$HERE/../../logs" +STAMP="$(date +%Y%m%d-%H%M%S)" +LOG="$LOG_DIR/enable-winrm-${HOST}-${STAMP}.log" + +mkdir -p "$LOG_DIR" + +SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10) + +echo "[*] Enabling WinRM on $USER_NAME@$HOST" +echo "[*] This will configure PowerShell Remoting and WinRM service" +echo "" +echo "⚠️ WARNING: This script will enable Basic authentication and unencrypted" +echo " communication for testing. Consider security implications for production." +echo "" +read -p "Continue? [y/N] " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Aborted." + exit 1 +fi + +echo "[*] Uploading WinRM enablement script..." +sshpass -p "$PASS" scp "${SSH_OPTS[@]}" "$SCRIPT" "$USER_NAME@$HOST:C:/Users/$USER_NAME/enable-winrm.ps1" >/dev/null + +echo "[*] Running WinRM configuration (requires Administrator), log -> $LOG" +echo "==================================================================" + +sshpass -p "$PASS" ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" \ + "powershell -NoProfile -ExecutionPolicy Bypass -File C:/Users/$USER_NAME/enable-winrm.ps1" \ + 2>&1 | tee "$LOG" + +echo "==================================================================" +echo "[*] WinRM configuration completed. Log saved: $LOG" +echo "" +echo "Next steps:" +echo " 1. Run ./check-winrm.sh to verify configuration" +echo " 2. Test WinRM connection from this machine" +echo " 3. If successful, update existing scripts to use WinRM" \ No newline at end of file diff --git a/scripts/ssh/firewall-allow-ssh.sh b/scripts/ssh/firewall-allow-ssh.sh new file mode 100755 index 0000000..2d911c5 --- /dev/null +++ b/scripts/ssh/firewall-allow-ssh.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${HOST:-192.168.88.108}" +USER_NAME="${USER_NAME:-Admin}" +PASS="${PASS:-P@ssw0rd!}" + +HERE="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$HERE/../powershell/firewall-allow-ssh.ps1" +LOG_DIR="$HERE/../../logs" +STAMP="$(date +%Y%m%d-%H%M%S)" +LOG="$LOG_DIR/firewall-allow-ssh-${HOST}-${STAMP}.log" + +mkdir -p "$LOG_DIR" + +SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10) + +echo "[*] Uploading firewall-allow-ssh.ps1 to $USER_NAME@$HOST" +sshpass -p "$PASS" scp "${SSH_OPTS[@]}" "$SCRIPT" \ + "$USER_NAME@$HOST:C:/Users/$USER_NAME/firewall-allow-ssh.ps1" >/dev/null + +echo "[*] Running on $HOST, log -> $LOG" +sshpass -p "$PASS" ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" \ + "powershell -NoProfile -ExecutionPolicy Bypass -File C:/Users/$USER_NAME/firewall-allow-ssh.ps1" \ + 2>&1 | tee "$LOG" + +echo "[*] Done. Saved: $LOG" diff --git a/scripts/ssh/fix-winrm.sh b/scripts/ssh/fix-winrm.sh new file mode 100755 index 0000000..0c679f5 --- /dev/null +++ b/scripts/ssh/fix-winrm.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${HOST:-192.168.88.108}" +USER_NAME="${USER_NAME:-Admin}" +PASS="${PASS:-P@ssw0rd!}" + +HERE="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$HERE/../powershell/fix-winrm.ps1" +LOG_DIR="$HERE/../../logs" +STAMP="$(date +%Y%m%d-%H%M%S)" +LOG="$LOG_DIR/fix-winrm-${HOST}-${STAMP}.log" + +mkdir -p "$LOG_DIR" + +SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10) + +echo "[*] Fixing remaining WinRM configuration issues on $USER_NAME@$HOST" +echo "[*] Uploading fix script..." + +sshpass -p "$PASS" scp "${SSH_OPTS[@]}" "$SCRIPT" "$USER_NAME@$HOST:C:/Users/$USER_NAME/fix-winrm.ps1" >/dev/null + +echo "[*] Running WinRM configuration fixes, log -> $LOG" +echo "==================================================================" + +sshpass -p "$PASS" ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" \ + "powershell -NoProfile -ExecutionPolicy Bypass -File C:/Users/$USER_NAME/fix-winrm.ps1" \ + 2>&1 | tee "$LOG" + +echo "==================================================================" +echo "[*] WinRM fixes completed. Log saved: $LOG" \ No newline at end of file diff --git a/scripts/ssh/install-apps.sh b/scripts/ssh/install-apps.sh new file mode 100755 index 0000000..16a646c --- /dev/null +++ b/scripts/ssh/install-apps.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${HOST:-192.168.88.108}" +USER_NAME="${USER_NAME:-Admin}" +PASS="${PASS:-P@ssw0rd!}" + +HERE="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$HERE/../powershell/install-apps.ps1" +LOG_DIR="$HERE/../../logs" +STAMP="$(date +%Y%m%d-%H%M%S)" +LOG="$LOG_DIR/install-apps-${HOST}-${STAMP}.log" + +mkdir -p "$LOG_DIR" + +SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10) + +echo "[*] 上傳應用程式安裝腳本到 $USER_NAME@$HOST" +sshpass -p "$PASS" scp "${SSH_OPTS[@]}" "$SCRIPT" \ + "$USER_NAME@$HOST:C:/Users/$USER_NAME/install-apps.ps1" >/dev/null + +echo "[*] 執行應用程式安裝 (AnyDesk, LINE, Chrome, 7zip, Adobe Reader)" +echo " 這可能需要 5-15 分鐘,依據網路速度而定" +echo " Log -> $LOG" +sshpass -p "$PASS" ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" \ + "powershell -NoProfile -ExecutionPolicy Bypass -File C:/Users/$USER_NAME/install-apps.ps1" \ + 2>&1 | tee "$LOG" + +echo "" +echo "[*] 安裝完成。紀錄已儲存: $LOG" \ No newline at end of file diff --git a/scripts/ssh/install-cports.sh b/scripts/ssh/install-cports.sh new file mode 100755 index 0000000..15784d2 --- /dev/null +++ b/scripts/ssh/install-cports.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${HOST:-192.168.88.108}" +USER_NAME="${USER_NAME:-Admin}" +PASS="${PASS:-P@ssw0rd!}" + +HERE="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$HERE/../powershell/install-cports.ps1" +LOG_DIR="$HERE/../../logs" +STAMP="$(date +%Y%m%d-%H%M%S)" +LOG="$LOG_DIR/install-cports-${HOST}-${STAMP}.log" + +mkdir -p "$LOG_DIR" + +SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10) + +echo "[*] Uploading install-cports.ps1 to $USER_NAME@$HOST" +sshpass -p "$PASS" scp "${SSH_OPTS[@]}" "$SCRIPT" \ + "$USER_NAME@$HOST:C:/Users/$USER_NAME/install-cports.ps1" >/dev/null + +echo "[*] Running on $HOST, log -> $LOG" +sshpass -p "$PASS" ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" \ + "powershell -NoProfile -ExecutionPolicy Bypass -File C:/Users/$USER_NAME/install-cports.ps1" \ + 2>&1 | tee "$LOG" + +echo "[*] Done. Saved: $LOG" diff --git a/scripts/ssh/install-nirsoft.sh b/scripts/ssh/install-nirsoft.sh new file mode 100755 index 0000000..0f43e58 --- /dev/null +++ b/scripts/ssh/install-nirsoft.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${HOST:-192.168.88.108}" +USER_NAME="${USER_NAME:-Admin}" +PASS="${PASS:-P@ssw0rd!}" + +HERE="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$HERE/../powershell/install-nirsoft.ps1" +CONFIG="$HERE/nirsoft-config.json" +LOG_DIR="$HERE/../../logs" +STAMP="$(date +%Y%m%d-%H%M%S)" +LOG="$LOG_DIR/install-nirsoft-${HOST}-${STAMP}.log" + +mkdir -p "$LOG_DIR" + +SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10 -o ServerAliveInterval=30) + +echo "[*] Uploading install-nirsoft.ps1 and nirsoft-config.json" +sshpass -p "$PASS" scp "${SSH_OPTS[@]}" "$SCRIPT" "$CONFIG" \ + "$USER_NAME@$HOST:C:/Users/$USER_NAME/" >/dev/null + +echo "[*] Running on $HOST — this downloads ~16 archives, may take a few minutes" +echo "[*] Log -> $LOG" +sshpass -p "$PASS" ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" \ + "powershell -NoProfile -ExecutionPolicy Bypass -File C:/Users/$USER_NAME/install-nirsoft.ps1" \ + 2>&1 | tee "$LOG" + +echo "[*] Done. Saved: $LOG" diff --git a/scripts/ssh/install-telegram.sh b/scripts/ssh/install-telegram.sh new file mode 100755 index 0000000..361df08 --- /dev/null +++ b/scripts/ssh/install-telegram.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${HOST:-192.168.88.108}" +USER_NAME="${USER_NAME:-Admin}" +PASS="${PASS:-P@ssw0rd!}" + +HERE="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$HERE/../powershell/install-telegram.ps1" +LOG_DIR="$HERE/../../logs" +STAMP="$(date +%Y%m%d-%H%M%S)" +LOG="$LOG_DIR/install-telegram-${HOST}-${STAMP}.log" + +mkdir -p "$LOG_DIR" + +SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10) + +echo "[*] Uploading install-telegram.ps1 to $USER_NAME@$HOST" +sshpass -p "$PASS" scp "${SSH_OPTS[@]}" "$SCRIPT" \ + "$USER_NAME@$HOST:C:/Users/$USER_NAME/install-telegram.ps1" >/dev/null + +echo "[*] Running installer on $HOST, log -> $LOG" +sshpass -p "$PASS" ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" \ + "powershell -NoProfile -ExecutionPolicy Bypass -File C:/Users/$USER_NAME/install-telegram.ps1" \ + 2>&1 | tee "$LOG" + +echo "[*] Done. Saved: $LOG" diff --git a/scripts/ssh/install-thunderbird.sh b/scripts/ssh/install-thunderbird.sh new file mode 100755 index 0000000..1e554e1 --- /dev/null +++ b/scripts/ssh/install-thunderbird.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${HOST:-192.168.88.108}" +USER_NAME="${USER_NAME:-Admin}" +PASS="${PASS:-P@ssw0rd!}" + +HERE="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$HERE/../powershell/install-thunderbird.ps1" +LOG_DIR="$HERE/../../logs" +STAMP="$(date +%Y%m%d-%H%M%S)" +LOG="$LOG_DIR/install-thunderbird-${HOST}-${STAMP}.log" + +mkdir -p "$LOG_DIR" + +SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10) + +echo "[*] Uploading install-thunderbird.ps1 to $USER_NAME@$HOST" +sshpass -p "$PASS" scp "${SSH_OPTS[@]}" "$SCRIPT" \ + "$USER_NAME@$HOST:C:/Users/$USER_NAME/install-thunderbird.ps1" >/dev/null + +echo "[*] Running installer on $HOST, log -> $LOG" +sshpass -p "$PASS" ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" \ + "powershell -NoProfile -ExecutionPolicy Bypass -File C:/Users/$USER_NAME/install-thunderbird.ps1" \ + 2>&1 | tee "$LOG" + +echo "[*] Done. Saved: $LOG" diff --git a/scripts/ssh/nirsoft-dump.sh b/scripts/ssh/nirsoft-dump.sh new file mode 100755 index 0000000..b8a9aee --- /dev/null +++ b/scripts/ssh/nirsoft-dump.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${HOST:-192.168.88.108}" +USER_NAME="${USER_NAME:-Admin}" +PASS="${PASS:-P@ssw0rd!}" + +HERE="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$HERE/../powershell/nirsoft-dump.ps1" +DUMP_DIR="$HERE/dumps" +LOG_DIR="$HERE/../../logs" +STAMP="$(date +%Y%m%d-%H%M%S)" +LOG="$LOG_DIR/nirsoft-dump-${HOST}-${STAMP}.log" + +mkdir -p "$DUMP_DIR" "$LOG_DIR" + +SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10) + +echo "[*] Uploading nirsoft-dump.ps1 to $USER_NAME@$HOST" +sshpass -p "$PASS" scp "${SSH_OPTS[@]}" "$SCRIPT" \ + "$USER_NAME@$HOST:C:/Users/$USER_NAME/nirsoft-dump.ps1" >/dev/null + +echo "[*] Running dump on $HOST (this takes ~1-2 min), log -> $LOG" +OUT=$(sshpass -p "$PASS" ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" \ + "powershell -NoProfile -ExecutionPolicy Bypass -File C:/Users/$USER_NAME/nirsoft-dump.ps1" \ + 2>&1 | tee "$LOG") + +REMOTE_ZIP=$(echo "$OUT" | grep '^DUMP_ZIP:' | awk '{print $2}' | tr -d '\r') +REMOTE_SIZE=$(echo "$OUT" | grep '^DUMP_SIZE:' | awk '{print $2}' | tr -d '\r') + +if [[ -z "$REMOTE_ZIP" ]]; then + echo "[!] Could not find DUMP_ZIP line in output" >&2 + exit 1 +fi + +# Convert Windows path to forward-slash form for scp + basename +REMOTE_ZIP_SCP=$(echo "$REMOTE_ZIP" | sed 's|\\|/|g') +LOCAL_ZIP="$DUMP_DIR/$(basename "$REMOTE_ZIP_SCP")" + +echo "" +echo "[*] Pulling zip back: $REMOTE_ZIP_SCP (${REMOTE_SIZE} bytes) -> $LOCAL_ZIP" +sshpass -p "$PASS" scp "${SSH_OPTS[@]}" \ + "$USER_NAME@$HOST:$REMOTE_ZIP_SCP" "$LOCAL_ZIP" >/dev/null + +# Clean up remote copy +sshpass -p "$PASS" ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" \ + "del \"$REMOTE_ZIP\"" >/dev/null 2>&1 || true + +echo "" +echo "[OK] Dump saved: $LOCAL_ZIP" +echo " Unzip with: unzip -l \"$LOCAL_ZIP\" (to list)" +echo " unzip \"$LOCAL_ZIP\" -d \"${LOCAL_ZIP%.zip}/\" (to extract)" diff --git a/scripts/ssh/recon.sh b/scripts/ssh/recon.sh new file mode 100755 index 0000000..14954e8 --- /dev/null +++ b/scripts/ssh/recon.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${HOST:-192.168.88.108}" +USER_NAME="${USER_NAME:-Admin}" +PASS="${PASS:-P@ssw0rd!}" + +HERE="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$HERE/../powershell/recon.ps1" +LOG_DIR="$HERE/../../logs" +STAMP="$(date +%Y%m%d-%H%M%S)" +LOG="$LOG_DIR/recon-${HOST}-${STAMP}.log" + +mkdir -p "$LOG_DIR" + +SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10) + +echo "[*] Uploading recon.ps1 to $USER_NAME@$HOST" +sshpass -p "$PASS" scp "${SSH_OPTS[@]}" "$SCRIPT" "$USER_NAME@$HOST:C:/Users/$USER_NAME/recon.ps1" >/dev/null + +echo "[*] Running recon on $HOST, log -> $LOG" +sshpass -p "$PASS" ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" \ + "powershell -NoProfile -ExecutionPolicy Bypass -File C:/Users/$USER_NAME/recon.ps1" \ + 2>&1 | tee "$LOG" + +echo "[*] Done. Saved: $LOG" diff --git a/scripts/ssh/remove-apps.sh b/scripts/ssh/remove-apps.sh new file mode 100755 index 0000000..9e755b2 --- /dev/null +++ b/scripts/ssh/remove-apps.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${HOST:-192.168.88.108}" +USER_NAME="${USER_NAME:-Admin}" +PASS="${PASS:-P@ssw0rd!}" + +HERE="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$HERE/../powershell/remove-apps.ps1" +CONFIG="$HERE/remove-apps-config.json" +LOG_DIR="$HERE/../../logs" +STAMP="$(date +%Y%m%d-%H%M%S)" +LOG="$LOG_DIR/remove-apps-${HOST}-${STAMP}.log" + +mkdir -p "$LOG_DIR" + +SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10) + +echo "[*] Uploading remove-apps.ps1 and remove-apps-config.json" +sshpass -p "$PASS" scp "${SSH_OPTS[@]}" "$SCRIPT" "$CONFIG" \ + "$USER_NAME@$HOST:C:/Users/$USER_NAME/" >/dev/null + +echo "[*] Running on $HOST, log -> $LOG" +sshpass -p "$PASS" ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" \ + "powershell -NoProfile -ExecutionPolicy Bypass -File C:/Users/$USER_NAME/remove-apps.ps1" \ + 2>&1 | tee "$LOG" + +echo "[*] Done. Saved: $LOG" diff --git a/scripts/ssh/remove-windows-ai.sh b/scripts/ssh/remove-windows-ai.sh new file mode 100755 index 0000000..fff69e4 --- /dev/null +++ b/scripts/ssh/remove-windows-ai.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${HOST:-192.168.88.108}" +USER_NAME="${USER_NAME:-Admin}" +PASS="${PASS:-P@ssw0rd!}" +MODE="${MODE:-Apply}" # Apply | Revert + +HERE="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$HERE/../powershell/remove-windows-ai.ps1" +LOG_DIR="$HERE/../../logs" +STAMP="$(date +%Y%m%d-%H%M%S)" +LOG="$LOG_DIR/remove-windows-ai-${HOST}-${MODE}-${STAMP}.log" + +mkdir -p "$LOG_DIR" + +SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10 -o ServerAliveInterval=30) + +echo "[*] Uploading remove-windows-ai.ps1 to $USER_NAME@$HOST" +sshpass -p "$PASS" scp "${SSH_OPTS[@]}" "$SCRIPT" \ + "$USER_NAME@$HOST:C:/Users/$USER_NAME/remove-windows-ai.ps1" >/dev/null + +echo "[*] Mode=$MODE. Running on $HOST — this can take several minutes" +echo "[*] Log -> $LOG" +# Force Windows PowerShell 5.1 (powershell.exe), not pwsh 7. +sshpass -p "$PASS" ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" \ + "powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:/Users/$USER_NAME/remove-windows-ai.ps1 -Mode $MODE" \ + 2>&1 | tee "$LOG" + +echo "[*] Done. Saved: $LOG" +echo "" +echo "NOTE: A reboot is recommended. Reboot with:" +echo " sshpass -p '$PASS' ssh $USER_NAME@$HOST 'shutdown /r /t 5'" diff --git a/scripts/ssh/rename-hide-accounts.sh b/scripts/ssh/rename-hide-accounts.sh new file mode 100755 index 0000000..3ab71bf --- /dev/null +++ b/scripts/ssh/rename-hide-accounts.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${HOST:-192.168.88.108}" +USER_NAME="${USER_NAME:-Admin}" +PASS="${PASS:-P@ssw0rd!}" + +HERE="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$HERE/../powershell/rename-hide-accounts.ps1" +LOG_DIR="$HERE/../../logs" +STAMP="$(date +%Y%m%d-%H%M%S)" +LOG="$LOG_DIR/rename-hide-accounts-${HOST}-${STAMP}.log" + +mkdir -p "$LOG_DIR" + +SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10) + +echo "[*] Uploading rename-hide-accounts.ps1 to $USER_NAME@$HOST" +sshpass -p "$PASS" scp "${SSH_OPTS[@]}" "$SCRIPT" \ + "$USER_NAME@$HOST:C:/Users/$USER_NAME/rename-hide-accounts.ps1" >/dev/null + +echo "[*] Running on $HOST, log -> $LOG" +sshpass -p "$PASS" ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" \ + "powershell -NoProfile -ExecutionPolicy Bypass -File C:/Users/$USER_NAME/rename-hide-accounts.ps1" \ + 2>&1 | tee "$LOG" + +echo "[*] Done. Saved: $LOG" diff --git a/scripts/ssh/setup-thunderbird.sh b/scripts/ssh/setup-thunderbird.sh new file mode 100755 index 0000000..0042be7 --- /dev/null +++ b/scripts/ssh/setup-thunderbird.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${HOST:-192.168.88.108}" +USER_NAME="${USER_NAME:-Admin}" +PASS="${PASS:-P@ssw0rd!}" + +HERE="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$HERE/../powershell/setup-thunderbird.ps1" +CONFIG="$HERE/thunderbird-config.json" +LOG_DIR="$HERE/../../logs" +STAMP="$(date +%Y%m%d-%H%M%S)" +LOG="$LOG_DIR/setup-thunderbird-${HOST}-${STAMP}.log" + +mkdir -p "$LOG_DIR" + +SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10) + +if [[ ! -f "$CONFIG" ]]; then + echo "[!] Missing $CONFIG — please edit thunderbird-config.json first" >&2 + exit 1 +fi + +echo "[*] Uploading setup-thunderbird.ps1 and thunderbird-config.json" +sshpass -p "$PASS" scp "${SSH_OPTS[@]}" "$SCRIPT" "$CONFIG" \ + "$USER_NAME@$HOST:C:/Users/$USER_NAME/" >/dev/null + +echo "[*] Running setup on $HOST, log -> $LOG" +sshpass -p "$PASS" ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" \ + "powershell -NoProfile -ExecutionPolicy Bypass -File C:/Users/$USER_NAME/setup-thunderbird.ps1" \ + 2>&1 | tee "$LOG" + +echo "[*] Done. Saved: $LOG" diff --git a/scripts/ssh/test-winrm-connection.sh b/scripts/ssh/test-winrm-connection.sh new file mode 100755 index 0000000..576b42e --- /dev/null +++ b/scripts/ssh/test-winrm-connection.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${HOST:-192.168.88.108}" +USER_NAME="${USER_NAME:-Admin}" +PASS="${PASS:-P@ssw0rd!}" + +echo "=== Testing WinRM Connection to $HOST ===" +echo "" + +# Test 1: Check if PowerShell Core is installed +echo "[1/4] Checking PowerShell Core availability..." +if command -v pwsh >/dev/null 2>&1; then + echo "✅ PowerShell Core found: $(pwsh --version)" +else + echo "❌ PowerShell Core not found" + echo "Install with: brew install powershell" + exit 1 +fi + +# Test 2: Test basic network connectivity to WinRM port +echo "" +echo "[2/4] Testing network connectivity to WinRM port 5985..." +if nc -z "$HOST" 5985; then + echo "✅ Port 5985 is reachable" +else + echo "❌ Cannot reach port 5985 on $HOST" + exit 1 +fi + +# Test 3: Test HTTP WinRM endpoint with curl +echo "" +echo "[3/4] Testing WinRM HTTP endpoint..." +WINRM_RESPONSE=$(curl -s -w "%{http_code}" -o /dev/null --max-time 10 \ + "http://$HOST:5985/wsman" \ + -H "Content-Type: application/soap+xml;charset=UTF-8" \ + -H "SOAPAction: http://schemas.xmlsoap.org/ws/2004/09/enumeration/Enumerate" \ + --data 'SELECT * FROM Win32_Service' 2>/dev/null || echo "000") + +if [ "$WINRM_RESPONSE" = "401" ]; then + echo "✅ WinRM endpoint responding (401 = authentication required, expected)" +elif [ "$WINRM_RESPONSE" = "500" ]; then + echo "✅ WinRM endpoint responding (500 = server error, but WinRM is there)" +else + echo "⚠️ WinRM endpoint response: $WINRM_RESPONSE (may still work with PowerShell)" +fi + +# Test 4: Test PowerShell remoting +echo "" +echo "[4/4] Testing PowerShell remoting session..." +pwsh -c " +try { + \$secpass = ConvertTo-SecureString '$PASS' -AsPlainText -Force + \$cred = New-Object System.Management.Automation.PSCredential('$USER_NAME', \$secpass) + + Write-Host 'Attempting to create PowerShell session...' + \$sessionOption = New-PSSessionOption -SkipCACheck -SkipCNCheck + \$session = New-PSSession -ComputerName '$HOST' -Port 5985 -Credential \$cred -UseSSL:\$false -SessionOption \$sessionOption -ErrorAction Stop + + Write-Host '✅ PowerShell remoting session created successfully' + + # Test running a simple command + \$result = Invoke-Command -Session \$session -ScriptBlock { + @{ + ComputerName = \$env:COMPUTERNAME + UserName = \$env:USERNAME + PSVersion = \$PSVersionTable.PSVersion.ToString() + CurrentTime = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' + } + } + + Write-Host 'Remote session info:' + Write-Host ' Computer:' \$result.ComputerName + Write-Host ' User:' \$result.UserName + Write-Host ' PowerShell:' \$result.PSVersion + Write-Host ' Time:' \$result.CurrentTime + + Remove-PSSession \$session + Write-Host '✅ Session closed successfully' + +} catch { + Write-Host '❌ PowerShell remoting failed:' \$_.Exception.Message + Write-Host '' + Write-Host 'Common causes:' + Write-Host ' - AllowUnencrypted=false (current setting on target)' + Write-Host ' - Firewall blocking connections' + Write-Host ' - Network connectivity issues' + Write-Host ' - Authentication problems' + exit 1 +} +" + +echo "" +echo "🎉 All WinRM connectivity tests passed!" +echo "" +echo "WinRM is ready for use. You can now:" +echo " 1. Create WinRM-based versions of existing scripts" +echo " 2. Use PowerShell remoting for direct command execution" +echo " 3. Compare performance with SSH-based approach" \ No newline at end of file diff --git a/scripts/ssh/verify-ai-removed.sh b/scripts/ssh/verify-ai-removed.sh new file mode 100755 index 0000000..5072f70 --- /dev/null +++ b/scripts/ssh/verify-ai-removed.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${HOST:-192.168.88.108}" +USER_NAME="${USER_NAME:-Admin}" +PASS="${PASS:-P@ssw0rd!}" + +HERE="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$HERE/../powershell/verify-ai-removed.ps1" +LOG_DIR="$HERE/../../logs" +STAMP="$(date +%Y%m%d-%H%M%S)" +LOG="$LOG_DIR/verify-ai-removed-${HOST}-${STAMP}.log" + +mkdir -p "$LOG_DIR" + +SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10) + +echo "[*] Uploading verify-ai-removed.ps1 to $USER_NAME@$HOST" +sshpass -p "$PASS" scp "${SSH_OPTS[@]}" "$SCRIPT" \ + "$USER_NAME@$HOST:C:/Users/$USER_NAME/verify-ai-removed.ps1" >/dev/null + +echo "[*] Running verify on $HOST, log -> $LOG" +sshpass -p "$PASS" ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" \ + "powershell -NoProfile -ExecutionPolicy Bypass -File C:/Users/$USER_NAME/verify-ai-removed.ps1" \ + 2>&1 | tee "$LOG" + +echo "[*] Done. Saved: $LOG" diff --git a/scripts/ssh/verify-debloat.sh b/scripts/ssh/verify-debloat.sh new file mode 100755 index 0000000..8f9f074 --- /dev/null +++ b/scripts/ssh/verify-debloat.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${HOST:-192.168.88.108}" +USER_NAME="${USER_NAME:-Admin}" +PASS="${PASS:-P@ssw0rd!}" + +HERE="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$HERE/../powershell/verify-debloat.ps1" +LOG_DIR="$HERE/../../logs" +STAMP="$(date +%Y%m%d-%H%M%S)" +LOG="$LOG_DIR/verify-debloat-${HOST}-${STAMP}.log" + +mkdir -p "$LOG_DIR" + +SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10) + +echo "[*] Uploading verify-debloat.ps1 to $USER_NAME@$HOST" +sshpass -p "$PASS" scp "${SSH_OPTS[@]}" "$SCRIPT" \ + "$USER_NAME@$HOST:C:/Users/$USER_NAME/verify-debloat.ps1" >/dev/null + +echo "[*] Running verify on $HOST, log -> $LOG" +sshpass -p "$PASS" ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" \ + "powershell -NoProfile -ExecutionPolicy Bypass -File C:/Users/$USER_NAME/verify-debloat.ps1" \ + 2>&1 | tee "$LOG" + +echo "[*] Done. Saved: $LOG" diff --git a/scripts/ssh/win11debloat.sh b/scripts/ssh/win11debloat.sh new file mode 100755 index 0000000..5ca2281 --- /dev/null +++ b/scripts/ssh/win11debloat.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="${HOST:-192.168.88.108}" +USER_NAME="${USER_NAME:-Admin}" +PASS="${PASS:-P@ssw0rd!}" + +HERE="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$HERE/../powershell/win11debloat.ps1" +CONFIG="$HERE/win11debloat-config.json" +LOG_DIR="$HERE/../../logs" +STAMP="$(date +%Y%m%d-%H%M%S)" +LOG="$LOG_DIR/win11debloat-${HOST}-${STAMP}.log" + +mkdir -p "$LOG_DIR" + +SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10 -o ServerAliveInterval=30) + +if [[ ! -f "$CONFIG" ]]; then + echo "[!] $CONFIG missing" >&2 + exit 1 +fi + +echo "[*] Uploading win11debloat.ps1 and win11debloat-config.json" +sshpass -p "$PASS" scp "${SSH_OPTS[@]}" "$SCRIPT" "$CONFIG" \ + "$USER_NAME@$HOST:C:/Users/$USER_NAME/" >/dev/null + +echo "[*] Running on $HOST — this may take several minutes" +echo "[*] Log -> $LOG" +sshpass -p "$PASS" ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" \ + "powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:/Users/$USER_NAME/win11debloat.ps1" \ + 2>&1 | tee "$LOG" + +echo "[*] Done. Saved: $LOG" diff --git a/scripts/winrm/benchmark_ssh_vs_winrm.py b/scripts/winrm/benchmark_ssh_vs_winrm.py new file mode 100644 index 0000000..91bfa7f --- /dev/null +++ b/scripts/winrm/benchmark_ssh_vs_winrm.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +""" +Benchmark SSH vs WinRM performance +Compares execution times and capabilities +""" + +import time +import subprocess +import os +from winrm import Session + + +def benchmark_ssh_method(): + """Benchmark SSH-based script execution""" + print("=== SSH Method Benchmark ===") + + start_time = time.time() + + try: + # Run a simple recon via SSH + result = subprocess.run( + ['timeout', '30', './recon.sh'], + capture_output=True, + text=True, + timeout=35 + ) + + end_time = time.time() + duration = end_time - start_time + + if result.returncode == 0: + print(f"✅ SSH method successful") + print(f"⏱️ Execution time: {duration:.2f} seconds") + return duration, True + else: + print(f"❌ SSH method failed: {result.stderr}") + return duration, False + + except subprocess.TimeoutExpired: + print(f"❌ SSH method timed out after 35 seconds") + return 35, False + except Exception as e: + end_time = time.time() + duration = end_time - start_time + print(f"❌ SSH method error: {e}") + return duration, False + + +def benchmark_winrm_method(): + """Benchmark WinRM-based script execution""" + print("\n=== WinRM Method Benchmark ===") + + host = os.getenv('HOST', '192.168.88.108') + username = os.getenv('USER_NAME', 'Admin') + password = os.getenv('PASS', 'P@ssw0rd!') + + start_time = time.time() + + try: + # Connect to WinRM + session = Session( + f'http://{host}:5985/wsman', + auth=(username, password), + transport='ntlm' + ) + + # Run equivalent recon commands via WinRM + recon_script = """ + Write-Host "" + Write-Host "======== Computer ========" + Get-ComputerInfo -Property CsName,WindowsProductName,OsVersion,OsInstallDate | Format-List + + Write-Host "======== Boot Time ========" + (Get-CimInstance Win32_OperatingSystem).LastBootUpTime + + Write-Host "======== Local Users ========" + Get-LocalUser | Select-Object Name,Enabled,LastLogon | Format-Table -AutoSize + + Write-Host "======== Disks ========" + Get-Volume | Where-Object DriveLetter | Select-Object DriveLetter,FileSystemLabel, + @{n='Size(GB)';e={[math]::Round($_.Size / 1GB, 1)}}, + @{n='Free(GB)';e={[math]::Round($_.SizeRemaining / 1GB, 1)}} | Format-Table -AutoSize + + Write-Host "======== IPv4 Addresses ========" + Get-NetIPAddress -AddressFamily IPv4 | Where-Object { $_.InterfaceAlias -notmatch 'Loopback' } | + Select-Object InterfaceAlias,IPAddress | Format-Table -AutoSize + + Write-Host "======== WinRM Recon Completed ========" + """ + + result = session.run_ps(recon_script) + + end_time = time.time() + duration = end_time - start_time + + if result.status_code == 0: + print(f"✅ WinRM method successful") + print(f"⏱️ Execution time: {duration:.2f} seconds") + + # Show some output + output = result.std_out.decode('utf-8', errors='replace') + lines = [line.strip() for line in output.split('\n') if line.strip()] + print(f"📊 Output lines: {len(lines)}") + + return duration, True + else: + print(f"❌ WinRM method failed: {result.std_err.decode()}") + return duration, False + + except Exception as e: + end_time = time.time() + duration = end_time - start_time + print(f"❌ WinRM method error: {e}") + return duration, False + + +def main(): + print("🚀 SSH vs WinRM Performance Benchmark") + print("=====================================") + + # Benchmark SSH + ssh_time, ssh_success = benchmark_ssh_method() + + # Benchmark WinRM + winrm_time, winrm_success = benchmark_winrm_method() + + # Results comparison + print("\n📊 RESULTS COMPARISON") + print("=====================") + + print(f"SSH Method: {ssh_time:.2f}s {'✅' if ssh_success else '❌'}") + print(f"WinRM Method: {winrm_time:.2f}s {'✅' if winrm_success else '❌'}") + + if ssh_success and winrm_success: + if ssh_time < winrm_time: + speedup = winrm_time / ssh_time + print(f"\n🏃 SSH is {speedup:.1f}x faster than WinRM") + else: + speedup = ssh_time / winrm_time + print(f"\n🏃 WinRM is {speedup:.1f}x faster than SSH") + + print(f"\nTime difference: {abs(ssh_time - winrm_time):.2f} seconds") + + print("\n🎯 RECOMMENDATION") + print("=================") + + if ssh_success and winrm_success: + if ssh_time < winrm_time * 1.5: # If SSH is not significantly slower + print("✅ Continue using SSH method:") + print(" - Proven stability") + print(" - Good performance") + print(" - Handles large scripts well") + print(" - Simpler setup") + else: + print("✅ Consider WinRM method for:") + print(" - Better Windows integration") + print(" - Advanced PowerShell features") + print(" - Structured error handling") + elif ssh_success: + print("✅ SSH method is more reliable for this environment") + elif winrm_success: + print("✅ WinRM method works, SSH has issues") + else: + print("❌ Both methods have issues, investigate connectivity") + + print("\n💡 HYBRID APPROACH") + print("==================") + print("Consider using both methods strategically:") + print("- SSH: Large scripts, file operations, stability") + print("- WinRM: Quick commands, Windows-specific tasks, real-time interaction") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/winrm/generate_winrm_wrappers.py b/scripts/winrm/generate_winrm_wrappers.py new file mode 100755 index 0000000..64954ff --- /dev/null +++ b/scripts/winrm/generate_winrm_wrappers.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +""" +Generate WinRM wrapper scripts for existing PowerShell scripts +Creates Python WinRM alternatives to SSH-based scripts +""" + +import os +import sys +from pathlib import Path +import json + + +def create_winrm_wrapper(ps_script_name, description=""): + """Create a Python WinRM wrapper for a PowerShell script""" + + script_name_base = ps_script_name.replace('.ps1', '') + wrapper_name = f"{script_name_base}-winrm.py" + + template = f'''#!/usr/bin/env python3 +""" +WinRM version of {ps_script_name} +{description} +""" + +import os +import sys +from winrm_executor import WinRMExecutor +from pathlib import Path + + +def main(): + # Get connection details + host = os.getenv('HOST', '192.168.88.108') + username = os.getenv('USER_NAME', 'Admin') + password = os.getenv('PASS', 'P@ssw0rd!') + + print(f"=== WinRM Execution: {{username}}@{{host}} - {ps_script_name} ===") + + # Create WinRM executor + executor = WinRMExecutor(host, username, password) + + try: + # Test connection + print("[*] Establishing WinRM connection...") + if not executor.connect(): + print("❌ Failed to establish WinRM connection") + print("\\nTroubleshooting:") + print(" 1. Ensure target machine is powered on and reachable") + print(" 2. Verify WinRM service is running") + print(" 3. Check firewall and network connectivity") + sys.exit(1) + + print("✅ WinRM connection established") + + # Execute PowerShell script + script_path = Path(__file__).parent / "{ps_script_name}" + if not script_path.exists(): + print(f"❌ PowerShell script not found: {{script_path}}") + sys.exit(1) + + print(f"[*] Executing {ps_script_name} via WinRM...") + print("=" * 60) + + success = executor.upload_and_execute_script(script_path) + + print("=" * 60) + if success: + print(f"🎉 {{script_path.name}} completed successfully via WinRM!") + else: + print(f"❌ {{script_path.name}} failed") + sys.exit(1) + + finally: + executor.close() + + +if __name__ == "__main__": + main() +''' + + return wrapper_name, template + + +def main(): + current_dir = Path(__file__).parent + + # List of PowerShell scripts and their descriptions + scripts = { + 'recon.ps1': 'System reconnaissance and inventory', + 'activate-windows.ps1': 'Activate Windows using KMS method', + 'remove-windows-ai.ps1': 'Remove Windows AI components', + 'win11debloat.ps1': 'Remove Windows bloatware and disable telemetry', + 'verify-ai-removed.ps1': 'Verify Windows AI removal', + 'verify-debloat.ps1': 'Verify Windows debloating changes', + 'install-thunderbird.ps1': 'Install Thunderbird email client', + 'setup-thunderbird.ps1': 'Configure Thunderbird IMAP/SMTP', + 'install-telegram.ps1': 'Install Telegram Desktop', + 'install-nirsoft.ps1': 'Install NirSoft diagnostic tools', + 'nirsoft-dump.ps1': 'Collect system diagnostic data', + 'firewall-allow-ssh.ps1': 'Configure firewall for SSH access', + 'apply-ui-to-user.ps1': 'Apply UI settings to user', + 'configure-ime.ps1': 'Configure input method settings', + 'rename-hide-accounts.ps1': 'Rename and hide user accounts', + 'install-cports.ps1': 'Install CurrPorts network tool', + 'remove-apps.ps1': 'Remove specified applications' + } + + print("=== Generating WinRM Wrapper Scripts ===") + print() + + created_count = 0 + for ps_script, description in scripts.items(): + ps_path = current_dir / ps_script + + if ps_path.exists(): + wrapper_name, wrapper_content = create_winrm_wrapper(ps_script, description) + wrapper_path = current_dir / wrapper_name + + # Only create if it doesn't exist or user confirms overwrite + if wrapper_path.exists(): + response = input(f"⚠️ {wrapper_name} exists. Overwrite? [y/N]: ") + if response.lower() != 'y': + print(f"⏭️ Skipping {wrapper_name}") + continue + + with open(wrapper_path, 'w', encoding='utf-8') as f: + f.write(wrapper_content) + + # Make executable + wrapper_path.chmod(0o755) + + print(f"✅ Created: {wrapper_name}") + created_count += 1 + else: + print(f"⚠️ PowerShell script not found: {ps_script}") + + print() + print(f"🎉 Generated {created_count} WinRM wrapper scripts") + print() + print("Usage examples:") + print(" python3 recon-winrm.py") + print(" HOST=pc-74269 python3 install-thunderbird-winrm.py") + print(" USER_NAME=SomeUser python3 apply-ui-to-user-winrm.py") + print() + print("Benefits of WinRM approach:") + print(" ✅ Native Windows remote management") + print(" ✅ Better PowerShell integration") + print(" ✅ Structured error handling") + print(" ✅ Automatic UTF-8 BOM handling for Chinese Windows") + print(" ✅ Detailed logging and progress reporting") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/winrm/recon-winrm.py b/scripts/winrm/recon-winrm.py new file mode 100755 index 0000000..74d6dca --- /dev/null +++ b/scripts/winrm/recon-winrm.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +""" +WinRM version of recon script +Demonstrates how to replace SSH-based scripts with Python WinRM +""" + +import os +import sys +from winrm_executor import WinRMExecutor +from pathlib import Path + + +def main(): + # Get connection details + host = os.getenv('HOST', '192.168.88.108') + username = os.getenv('USER_NAME', 'Admin') + password = os.getenv('PASS', 'P@ssw0rd!') + + print(f"=== WinRM Reconnaissance: {username}@{host} ===") + + # Create WinRM executor + executor = WinRMExecutor(host, username, password) + + try: + # Test connection + print("[*] Establishing WinRM connection...") + if not executor.connect(): + print("❌ Failed to establish WinRM connection") + print("\nTroubleshooting:") + print(" 1. Ensure target machine is powered on and reachable") + print(" 2. Verify WinRM service is running: Get-Service WinRM") + print(" 3. Check AllowUnencrypted setting: winrm get winrm/config/service") + print(" 4. Verify firewall rules: Get-NetFirewallRule -DisplayGroup '*Remote Management*'") + sys.exit(1) + + print("✅ WinRM connection established") + + # Execute recon script + script_path = Path(__file__).parent / "../powershell/recon.ps1" + if not script_path.exists(): + print(f"❌ PowerShell script not found: {script_path}") + sys.exit(1) + + print(f"[*] Executing reconnaissance script via WinRM...") + print("=" * 60) + + success = executor.upload_and_execute_script(script_path) + + print("=" * 60) + if success: + print("🎉 Reconnaissance completed successfully via WinRM!") + else: + print("❌ Reconnaissance failed") + sys.exit(1) + + finally: + executor.close() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/winrm/smart_executor.py b/scripts/winrm/smart_executor.py new file mode 100755 index 0000000..d76ab01 --- /dev/null +++ b/scripts/winrm/smart_executor.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +""" +Smart Executor - Automatically choose between SSH and WinRM +Based on script size and complexity +""" + +import os +import sys +import subprocess +from pathlib import Path +from winrm import Session + + +class SmartExecutor: + def __init__(self, host=None, username=None, password=None): + self.host = host or os.getenv('HOST', '192.168.88.108') + self.username = username or os.getenv('USER_NAME', 'Admin') + self.password = password or os.getenv('PASS', 'P@ssw0rd!') + + def analyze_script(self, script_path): + """Analyze script to determine best execution method""" + script_path = Path(script_path) + + if not script_path.exists(): + return None + + # Get script size + size = script_path.stat().st_size + + # Read content for analysis + with open(script_path, 'r', encoding='utf-8') as f: + content = f.read() + + lines = len(content.split('\n')) + chars = len(content) + + analysis = { + 'size_bytes': size, + 'lines': lines, + 'characters': chars, + 'has_file_operations': any(keyword in content.lower() for keyword in + ['out-file', 'copy-item', 'move-item', 'new-item', 'remove-item']), + 'has_long_commands': any(len(line) > 500 for line in content.split('\n')), + 'complexity_score': lines + (chars / 100) + (size / 1000) + } + + return analysis + + def choose_method(self, script_path): + """Choose optimal execution method""" + analysis = self.analyze_script(script_path) + + if not analysis: + return "error" + + # Decision logic + if analysis['size_bytes'] > 5000: # Large scripts + return "ssh" + elif analysis['lines'] > 100: # Many lines + return "ssh" + elif analysis['has_file_operations']: # File operations + return "ssh" + elif analysis['has_long_commands']: # Very long command lines + return "ssh" + elif analysis['complexity_score'] > 150: # Complex scripts + return "ssh" + else: + return "winrm" # Simple, fast execution + + def execute_via_ssh(self, script_path): + """Execute script using SSH method""" + script_name = Path(script_path).stem + ssh_script = f"{script_name}.sh" + + if not Path(ssh_script).exists(): + print(f"❌ SSH wrapper {ssh_script} not found") + return False + + try: + result = subprocess.run([f'./{ssh_script}'], capture_output=False, timeout=300) + return result.returncode == 0 + except Exception as e: + print(f"❌ SSH execution failed: {e}") + return False + + def execute_via_winrm(self, script_path): + """Execute script using WinRM method""" + try: + # Read and execute script content directly + with open(script_path, 'r', encoding='utf-8') as f: + script_content = f.read() + + # Connect via WinRM + session = Session( + f'http://{self.host}:5985/wsman', + auth=(self.username, self.password), + transport='ntlm' + ) + + # Execute PowerShell + result = session.run_ps(script_content) + + # Output results + if result.std_out: + output = result.std_out.decode('utf-8', errors='replace') + for line in output.split('\n'): + if line.strip(): + print(line) + + if result.std_err: + error = result.std_err.decode('utf-8', errors='replace') + if error.strip(): + for line in error.split('\n'): + if line.strip(): + print(f"ERROR: {line}") + + return result.status_code == 0 + + except Exception as e: + print(f"❌ WinRM execution failed: {e}") + return False + + def execute(self, script_path): + """Smart execute with automatic method selection""" + script_path = Path(script_path) + + print(f"🧠 Analyzing script: {script_path.name}") + + analysis = self.analyze_script(script_path) + method = self.choose_method(script_path) + + print(f"📊 Script analysis:") + print(f" Size: {analysis['size_bytes']} bytes") + print(f" Lines: {analysis['lines']}") + print(f" Complexity: {analysis['complexity_score']:.1f}") + print(f" File operations: {analysis['has_file_operations']}") + + print(f"⚡ Selected method: {method.upper()}") + + if method == "ssh": + print("🔗 Executing via SSH (for large/complex scripts)") + return self.execute_via_ssh(script_path) + elif method == "winrm": + print("⚡ Executing via WinRM (for speed)") + return self.execute_via_winrm(script_path) + else: + print("❌ Unable to determine execution method") + return False + + +def main(): + if len(sys.argv) != 2: + print("Usage: python3 smart_executor.py ") + sys.exit(1) + + script_path = sys.argv[1] + executor = SmartExecutor() + + print("🎯 Smart PowerShell Executor") + print("============================") + + success = executor.execute(script_path) + + if success: + print(f"\n✅ Script execution completed successfully") + else: + print(f"\n❌ Script execution failed") + sys.exit(1) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/winrm/test_simple_winrm.py b/scripts/winrm/test_simple_winrm.py new file mode 100644 index 0000000..985536d --- /dev/null +++ b/scripts/winrm/test_simple_winrm.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +""" +Test simple WinRM script execution +Uses very small commands to avoid length limits +""" + +import os +from winrm import Session + + +def test_simple_script_execution(): + host = os.getenv('HOST', '192.168.88.108') + username = os.getenv('USER_NAME', 'Admin') + password = os.getenv('PASS', 'P@ssw0rd!') + + print(f"Testing simple WinRM script execution on {host}") + + try: + # Connect + session = Session( + f'http://{host}:5985/wsman', + auth=(username, password), + transport='ntlm' + ) + + # Test 1: Simple inline PowerShell + print("\n[Test 1] Running simple inline PowerShell:") + simple_ps = """ + Write-Host "=== Simple WinRM Test ===" + Write-Host "Computer: $env:COMPUTERNAME" + Write-Host "User: $env:USERNAME" + Write-Host "PowerShell: $($PSVersionTable.PSVersion)" + Write-Host "Time: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" + Write-Host "=== End Test ===" + """ + + result = session.run_ps(simple_ps) + + if result.status_code == 0: + print("✅ Simple PowerShell execution successful:") + output = result.std_out.decode('utf-8', errors='replace') + for line in output.split('\n'): + if line.strip(): + print(f" {line.strip()}") + else: + print(f"❌ Simple PowerShell failed: {result.std_err.decode()}") + return False + + # Test 2: Create small script file + print("\n[Test 2] Creating and executing small script file:") + + # Create a very small script + create_script = """ + $scriptContent = @' +Write-Host "Small script test" +Get-ComputerInfo -Property CsName, WindowsProductName | Format-List +Write-Host "Small script completed" +'@ + + $scriptContent | Out-File -FilePath 'C:\\Users\\Admin\\small_test.ps1' -Encoding UTF8 + Write-Host "Small script file created" + """ + + result = session.run_ps(create_script) + + if result.status_code == 0: + print("✅ Script file created successfully") + + # Execute the small script + exec_result = session.run_cmd('powershell -NoProfile -ExecutionPolicy Bypass -File C:\\Users\\Admin\\small_test.ps1') + + if exec_result.status_code == 0: + print("✅ Script execution successful:") + output = exec_result.std_out.decode('utf-8', errors='replace') + for line in output.split('\n'): + if line.strip(): + print(f" {line.strip()}") + return True + else: + print(f"❌ Script execution failed: {exec_result.std_err.decode()}") + return False + else: + print(f"❌ Script creation failed: {result.std_err.decode()}") + return False + + except Exception as e: + print(f"❌ Test failed: {e}") + return False + + +if __name__ == "__main__": + if test_simple_script_execution(): + print("\n🎉 Simple WinRM script execution works!") + print("This proves WinRM can execute PowerShell scripts.") + print("The issue is with large script handling, not basic functionality.") + else: + print("\n❌ Basic WinRM functionality failed") \ No newline at end of file diff --git a/scripts/winrm/test_winrm_negotiate.py b/scripts/winrm/test_winrm_negotiate.py new file mode 100644 index 0000000..b6289f5 --- /dev/null +++ b/scripts/winrm/test_winrm_negotiate.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +""" +Test WinRM with Negotiate authentication (instead of Basic) +""" + +import os +from winrm import Session + + +def test_negotiate_auth(): + host = os.getenv('HOST', '192.168.88.108') + username = os.getenv('USER_NAME', 'Admin') + password = os.getenv('PASS', 'P@ssw0rd!') + + print(f"Testing WinRM with Negotiate authentication to {host}") + + try: + # Try with negotiate/ntlm transport + session = Session( + f'http://{host}:5985/wsman', + auth=(username, password), + transport='ntlm' # Use NTLM instead of plaintext + ) + + print("✅ Session created with NTLM transport") + + # Test simple command + result = session.run_cmd('echo "NTLM test successful"') + + if result.status_code == 0: + print(f"✅ Command successful: {result.std_out.decode().strip()}") + return True + else: + print(f"❌ Command failed: {result.std_err.decode()}") + return False + + except Exception as e: + print(f"❌ NTLM authentication failed: {e}") + + # Try with ssl transport (even though we're using HTTP) + try: + print("Trying with SSL transport (ignore cert validation)...") + session = Session( + f'http://{host}:5985/wsman', + auth=(username, password), + transport='ssl', + server_cert_validation='ignore' + ) + + print("✅ Session created with SSL transport") + + result = session.run_cmd('echo "SSL transport test"') + + if result.status_code == 0: + print(f"✅ Command successful: {result.std_out.decode().strip()}") + return True + else: + print(f"❌ Command failed: {result.std_err.decode()}") + return False + + except Exception as e2: + print(f"❌ SSL transport also failed: {e2}") + + # Try with kerberos if available + try: + print("Trying with Kerberos transport...") + session = Session( + f'http://{host}:5985/wsman', + auth=(username, password), + transport='kerberos' + ) + + print("✅ Session created with Kerberos transport") + + result = session.run_cmd('echo "Kerberos test"') + + if result.status_code == 0: + print(f"✅ Command successful: {result.std_out.decode().strip()}") + return True + else: + print(f"❌ Command failed: {result.std_err.decode()}") + return False + + except Exception as e3: + print(f"❌ Kerberos transport failed: {e3}") + return False + + +if __name__ == "__main__": + success = test_negotiate_auth() + if success: + print("\n🎉 Alternative authentication method works!") + else: + print("\n❌ All authentication methods failed") + print("\nThis suggests the issue is with AllowUnencrypted=false") + print("We may need to force this setting or use HTTPS instead") \ No newline at end of file diff --git a/scripts/winrm/test_winrm_python.py b/scripts/winrm/test_winrm_python.py new file mode 100755 index 0000000..ac8186d --- /dev/null +++ b/scripts/winrm/test_winrm_python.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +""" +Python WinRM Connection Test +Tests WinRM connectivity using pywinrm library +""" + +import os +import sys +from winrm.protocol import Protocol +from winrm import Session +import xml.etree.ElementTree as ET + + +def test_basic_connection(host, username, password): + """Test basic WinRM connectivity""" + print(f"[1/4] Testing basic WinRM protocol connection to {host}...") + + try: + # Create a basic protocol connection + endpoint = f"http://{host}:5985/wsman" + p = Protocol( + endpoint=endpoint, + transport='plaintext', + username=username, + password=password + ) + + # Try a simple identify operation + result = p.send_message( + '' + '' + '' + '' + '' + '' + ) + + print("✅ Basic WinRM protocol connection successful") + + # Parse response to get some info + try: + root = ET.fromstring(result) + for elem in root.iter(): + if 'ProductVersion' in elem.tag: + print(f" Protocol Version: {elem.text}") + elif 'ProductVendor' in elem.tag: + print(f" Product Vendor: {elem.text}") + except: + pass + + return True + + except Exception as e: + print(f"❌ Basic connection failed: {e}") + return False + + +def test_session_connection(host, username, password): + """Test WinRM session connectivity""" + print(f"\n[2/4] Testing WinRM session connection...") + + try: + session = Session( + f'http://{host}:5985/wsman', + auth=(username, password), + transport='plaintext' + ) + + print("✅ WinRM session created successfully") + return session + + except Exception as e: + print(f"❌ Session creation failed: {e}") + return None + + +def test_command_execution(session): + """Test basic command execution""" + print(f"\n[3/4] Testing command execution...") + + try: + # Test simple command + result = session.run_cmd('echo "Hello from WinRM"') + + if result.status_code == 0: + print("✅ Command execution successful") + print(f" Output: {result.std_out.decode().strip()}") + else: + print(f"⚠️ Command execution completed with exit code: {result.status_code}") + print(f" Error: {result.std_err.decode().strip()}") + + return True + + except Exception as e: + print(f"❌ Command execution failed: {e}") + return False + + +def test_powershell_execution(session): + """Test PowerShell script execution""" + print(f"\n[4/4] Testing PowerShell execution...") + + try: + # Test PowerShell command + ps_script = """ + $info = @{ + ComputerName = $env:COMPUTERNAME + UserName = $env:USERNAME + PowerShellVersion = $PSVersionTable.PSVersion.ToString() + WindowsVersion = (Get-ComputerInfo -Property WindowsProductName).WindowsProductName + CurrentTime = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' + } + + Write-Host "=== Remote System Information ===" + Write-Host "Computer: $($info.ComputerName)" + Write-Host "User: $($info.UserName)" + Write-Host "PowerShell: $($info.PowerShellVersion)" + Write-Host "Windows: $($info.WindowsVersion)" + Write-Host "Time: $($info.CurrentTime)" + Write-Host "=== End Information ===" + """ + + result = session.run_ps(ps_script) + + if result.status_code == 0: + print("✅ PowerShell execution successful") + print("Remote system info:") + output = result.std_out.decode().strip() + for line in output.split('\n'): + if line.strip(): + print(f" {line.strip()}") + else: + print(f"⚠️ PowerShell execution completed with exit code: {result.status_code}") + if result.std_err: + print(f" Error: {result.std_err.decode().strip()}") + + return True + + except Exception as e: + print(f"❌ PowerShell execution failed: {e}") + return False + + +def main(): + # Get connection details from environment or use defaults + host = os.getenv('HOST', '192.168.88.108') + username = os.getenv('USER_NAME', 'Admin') + password = os.getenv('PASS', 'P@ssw0rd!') + + print("=== Python WinRM Connection Test ===") + print(f"Target: {username}@{host}") + print("") + + # Test sequence + success = True + + # Test 1: Basic connection + if not test_basic_connection(host, username, password): + success = False + print("\nℹ️ If basic connection fails, ensure:") + print(" - WinRM service is running on target") + print(" - Port 5985 is accessible") + print(" - AllowUnencrypted=true for HTTP connections") + sys.exit(1) + + # Test 2: Session creation + session = test_session_connection(host, username, password) + if not session: + success = False + sys.exit(1) + + # Test 3: Command execution + if not test_command_execution(session): + success = False + + # Test 4: PowerShell execution + if not test_powershell_execution(session): + success = False + + if success: + print(f"\n🎉 All tests passed! Python WinRM is working correctly.") + print(f"\nNext steps:") + print(f" 1. Create Python WinRM wrapper for existing scripts") + print(f" 2. Compare performance with SSH method") + print(f" 3. Implement error handling and logging") + else: + print(f"\n⚠️ Some tests failed. Check configuration and try again.") + sys.exit(1) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/winrm/winrm_executor.py b/scripts/winrm/winrm_executor.py new file mode 100755 index 0000000..0ad013d --- /dev/null +++ b/scripts/winrm/winrm_executor.py @@ -0,0 +1,250 @@ +#!/usr/bin/env python3 +""" +WinRM Script Executor +A Python-based alternative to SSH for executing PowerShell scripts on Windows machines. +""" + +import os +import sys +import argparse +import json +import time +from datetime import datetime +from pathlib import Path +from winrm import Session +import logging + + +class WinRMExecutor: + def __init__(self, host, username, password, port=5985, use_https=False): + self.host = host + self.username = username + self.password = password + self.port = port + self.use_https = use_https + self.session = None + + # Setup logging + self.setup_logging() + + def setup_logging(self): + """Setup logging configuration""" + log_dir = Path(__file__).parent / "../../logs" + log_dir.mkdir(exist_ok=True) + + timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") + log_file = log_dir / f"winrm-{self.host}-{timestamp}.log" + + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler(log_file), + logging.StreamHandler() + ] + ) + + self.logger = logging.getLogger(__name__) + self.logger.info(f"Starting WinRM session to {self.host}") + + def connect(self): + """Establish WinRM connection""" + try: + protocol = 'https' if self.use_https else 'http' + endpoint = f'{protocol}://{self.host}:{self.port}/wsman' + + self.logger.info(f"Connecting to {endpoint}") + + self.session = Session( + endpoint, + auth=(self.username, self.password), + transport='ntlm' if not self.use_https else 'ssl', + server_cert_validation='ignore' if self.use_https else 'validate' + ) + + # Test connection with a simple command + result = self.session.run_cmd('echo Connection test') + if result.status_code == 0: + self.logger.info("✅ WinRM connection established successfully") + return True + else: + self.logger.error(f"❌ Connection test failed: {result.std_err.decode()}") + return False + + except Exception as e: + self.logger.error(f"❌ Failed to connect: {e}") + return False + + def upload_and_execute_script(self, local_script_path, remote_script_name=None): + """Upload and execute a PowerShell script""" + if not self.session: + self.logger.error("No active WinRM session") + return False + + try: + # Read the local script + with open(local_script_path, 'r', encoding='utf-8') as f: + script_content = f.read() + + # Generate remote script name if not provided + if not remote_script_name: + script_name = Path(local_script_path).name + remote_script_name = f"C:\\Users\\{self.username}\\{script_name}" + + self.logger.info(f"Uploading script: {local_script_path} -> {remote_script_name}") + + # Create the script on remote machine + create_script = f""" + $scriptContent = @' +{script_content} +'@ + + # Ensure UTF-8 encoding with BOM for Chinese Windows + $utf8BOM = New-Object System.Text.UTF8Encoding($true) + [System.IO.File]::WriteAllText('{remote_script_name}', $scriptContent, $utf8BOM) + + Write-Host "Script uploaded to {remote_script_name}" + """ + + upload_result = self.session.run_ps(create_script) + + if upload_result.status_code != 0: + self.logger.error(f"Failed to upload script: {upload_result.std_err.decode()}") + return False + + self.logger.info("✅ Script uploaded successfully") + + # Execute the script + self.logger.info(f"Executing PowerShell script: {remote_script_name}") + + execute_command = f"PowerShell -NoProfile -ExecutionPolicy Bypass -File '{remote_script_name}'" + result = self.session.run_cmd(execute_command) + + # Output results + if result.std_out: + output = result.std_out.decode('utf-8', errors='replace') + self.logger.info("Script Output:") + for line in output.split('\n'): + if line.strip(): + print(line) + self.logger.info(line) + + if result.std_err: + error = result.std_err.decode('utf-8', errors='replace') + if error.strip(): + self.logger.warning("Script Errors:") + for line in error.split('\n'): + if line.strip(): + print(f"ERROR: {line}") + self.logger.warning(line) + + success = result.status_code == 0 + status = "✅ SUCCESS" if success else f"❌ FAILED (exit code: {result.status_code})" + self.logger.info(f"Script execution completed: {status}") + + return success + + except Exception as e: + self.logger.error(f"❌ Script execution failed: {e}") + return False + + def execute_command(self, command, use_powershell=False): + """Execute a single command""" + if not self.session: + self.logger.error("No active WinRM session") + return False + + try: + self.logger.info(f"Executing command: {command}") + + if use_powershell: + result = self.session.run_ps(command) + else: + result = self.session.run_cmd(command) + + # Output results + if result.std_out: + output = result.std_out.decode('utf-8', errors='replace') + for line in output.split('\n'): + if line.strip(): + print(line) + + if result.std_err: + error = result.std_err.decode('utf-8', errors='replace') + if error.strip(): + for line in error.split('\n'): + if line.strip(): + print(f"ERROR: {line}") + + return result.status_code == 0 + + except Exception as e: + self.logger.error(f"Command execution failed: {e}") + return False + + def close(self): + """Close the WinRM session""" + if self.session: + self.logger.info("Closing WinRM session") + # Note: pywinrm doesn't have an explicit close method + self.session = None + + +def main(): + parser = argparse.ArgumentParser(description='Execute PowerShell scripts via WinRM') + parser.add_argument('--host', default=os.getenv('HOST', '192.168.88.108'), + help='Target host (default: from HOST env var or 192.168.88.108)') + parser.add_argument('--username', default=os.getenv('USER_NAME', 'Admin'), + help='Username (default: from USER_NAME env var or Admin)') + parser.add_argument('--password', default=os.getenv('PASS', 'P@ssw0rd!'), + help='Password (default: from PASS env var)') + parser.add_argument('--port', type=int, default=5985, + help='WinRM port (default: 5985)') + parser.add_argument('--https', action='store_true', + help='Use HTTPS instead of HTTP') + parser.add_argument('--script', required=True, + help='PowerShell script file to execute') + parser.add_argument('--test-connection', action='store_true', + help='Test connection only, do not execute script') + + args = parser.parse_args() + + # Create executor + executor = WinRMExecutor( + host=args.host, + username=args.username, + password=args.password, + port=args.port, + use_https=args.https + ) + + try: + # Connect + if not executor.connect(): + print("❌ Failed to establish WinRM connection") + sys.exit(1) + + if args.test_connection: + print("✅ WinRM connection test successful") + return + + # Execute script + script_path = Path(args.script) + if not script_path.exists(): + print(f"❌ Script file not found: {script_path}") + sys.exit(1) + + success = executor.upload_and_execute_script(script_path) + + if success: + print(f"\n🎉 Script execution completed successfully") + else: + print(f"\n❌ Script execution failed") + sys.exit(1) + + finally: + executor.close() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/winrm/winrm_executor_v2.py b/scripts/winrm/winrm_executor_v2.py new file mode 100755 index 0000000..4562ae2 --- /dev/null +++ b/scripts/winrm/winrm_executor_v2.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python3 +""" +Improved WinRM Script Executor +Handles large scripts by uploading them in chunks +""" + +import os +import sys +import argparse +import json +import time +import base64 +from datetime import datetime +from pathlib import Path +from winrm import Session +import logging + + +class WinRMExecutorV2: + def __init__(self, host, username, password, port=5985, use_https=False): + self.host = host + self.username = username + self.password = password + self.port = port + self.use_https = use_https + self.session = None + + # Setup logging + self.setup_logging() + + def setup_logging(self): + """Setup logging configuration""" + log_dir = Path(__file__).parent / "../../logs" + log_dir.mkdir(exist_ok=True) + + timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") + log_file = log_dir / f"winrm-v2-{self.host}-{timestamp}.log" + + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler(log_file), + logging.StreamHandler() + ] + ) + + self.logger = logging.getLogger(__name__) + self.logger.info(f"Starting WinRM session to {self.host}") + + def connect(self): + """Establish WinRM connection""" + try: + protocol = 'https' if self.use_https else 'http' + endpoint = f'{protocol}://{self.host}:{self.port}/wsman' + + self.logger.info(f"Connecting to {endpoint}") + + self.session = Session( + endpoint, + auth=(self.username, self.password), + transport='ntlm' if not self.use_https else 'ssl', + server_cert_validation='ignore' if self.use_https else 'validate' + ) + + # Test connection with a simple command + result = self.session.run_cmd('echo Connection test') + if result.status_code == 0: + self.logger.info("✅ WinRM connection established successfully") + return True + else: + self.logger.error(f"❌ Connection test failed: {result.std_err.decode()}") + return False + + except Exception as e: + self.logger.error(f"❌ Failed to connect: {e}") + return False + + def upload_script_chunked(self, local_script_path, remote_script_name): + """Upload script in chunks to avoid command line length limits""" + try: + # Read the local script + with open(local_script_path, 'r', encoding='utf-8') as f: + script_content = f.read() + + self.logger.info(f"Uploading script in chunks: {local_script_path} -> {remote_script_name}") + + # Encode content as base64 to avoid special character issues + script_bytes = script_content.encode('utf-8') + script_b64 = base64.b64encode(script_bytes).decode('ascii') + + # Split into chunks of 8000 chars (safe for command line) + chunk_size = 8000 + chunks = [script_b64[i:i+chunk_size] for i in range(0, len(script_b64), chunk_size)] + + self.logger.info(f"Uploading {len(chunks)} chunks...") + + # Create the file and append chunks + for i, chunk in enumerate(chunks): + if i == 0: + # First chunk - create file + ps_command = f""" + $chunk = '{chunk}' + $chunk | Out-File -FilePath '{remote_script_name}.b64' -Encoding ASCII + """ + else: + # Subsequent chunks - append + ps_command = f""" + $chunk = '{chunk}' + $chunk | Out-File -FilePath '{remote_script_name}.b64' -Encoding ASCII -Append + """ + + result = self.session.run_ps(ps_command) + + if result.status_code != 0: + self.logger.error(f"Failed to upload chunk {i+1}: {result.std_err.decode()}") + return False + + if (i + 1) % 10 == 0: + self.logger.info(f"Uploaded {i+1}/{len(chunks)} chunks...") + + # Decode the base64 file to create the final script + decode_command = f""" + $b64Content = Get-Content '{remote_script_name}.b64' -Raw + $bytes = [System.Convert]::FromBase64String($b64Content) + + # Write with UTF-8 BOM for Chinese Windows compatibility + $utf8BOM = New-Object System.Text.UTF8Encoding($true) + [System.IO.File]::WriteAllBytes('{remote_script_name}', $bytes) + + # Clean up temporary file + Remove-Item '{remote_script_name}.b64' -ErrorAction SilentlyContinue + + Write-Host "Script uploaded successfully to {remote_script_name}" + """ + + result = self.session.run_ps(decode_command) + + if result.status_code == 0: + self.logger.info("✅ Script uploaded and decoded successfully") + return True + else: + self.logger.error(f"Failed to decode script: {result.std_err.decode()}") + return False + + except Exception as e: + self.logger.error(f"❌ Script upload failed: {e}") + return False + + def execute_script(self, remote_script_name): + """Execute the uploaded PowerShell script""" + try: + self.logger.info(f"Executing PowerShell script: {remote_script_name}") + + # Execute the script + execute_command = f"PowerShell -NoProfile -ExecutionPolicy Bypass -File '{remote_script_name}'" + result = self.session.run_cmd(execute_command) + + # Output results in real-time + if result.std_out: + output = result.std_out.decode('utf-8', errors='replace') + self.logger.info("Script Output:") + for line in output.split('\n'): + if line.strip(): + print(line) + + if result.std_err: + error = result.std_err.decode('utf-8', errors='replace') + if error.strip(): + self.logger.warning("Script Errors:") + for line in error.split('\n'): + if line.strip(): + print(f"ERROR: {line}") + + success = result.status_code == 0 + status = "✅ SUCCESS" if success else f"❌ FAILED (exit code: {result.status_code})" + self.logger.info(f"Script execution completed: {status}") + + return success + + except Exception as e: + self.logger.error(f"❌ Script execution failed: {e}") + return False + + def upload_and_execute_script(self, local_script_path, remote_script_name=None): + """Upload and execute a PowerShell script using chunked upload""" + if not self.session: + self.logger.error("No active WinRM session") + return False + + # Generate remote script name if not provided + if not remote_script_name: + script_name = Path(local_script_path).name + remote_script_name = f"C:\\Users\\{self.username}\\{script_name}" + + # Upload script + if not self.upload_script_chunked(local_script_path, remote_script_name): + return False + + # Execute script + return self.execute_script(remote_script_name) + + def close(self): + """Close the WinRM session""" + if self.session: + self.logger.info("Closing WinRM session") + self.session = None + + +def main(): + parser = argparse.ArgumentParser(description='Execute PowerShell scripts via WinRM (v2)') + parser.add_argument('--host', default=os.getenv('HOST', '192.168.88.108'), + help='Target host') + parser.add_argument('--username', default=os.getenv('USER_NAME', 'Admin'), + help='Username') + parser.add_argument('--password', default=os.getenv('PASS', 'P@ssw0rd!'), + help='Password') + parser.add_argument('--script', required=True, + help='PowerShell script file to execute') + + args = parser.parse_args() + + # Create executor + executor = WinRMExecutorV2( + host=args.host, + username=args.username, + password=args.password + ) + + try: + # Connect + if not executor.connect(): + sys.exit(1) + + # Execute script + script_path = Path(args.script) + if not script_path.exists(): + print(f"❌ Script file not found: {script_path}") + sys.exit(1) + + success = executor.upload_and_execute_script(script_path) + + if success: + print(f"\n🎉 Script execution completed successfully") + else: + sys.exit(1) + + finally: + executor.close() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/ssh_run.sh b/ssh_run.sh new file mode 100755 index 0000000..47cd216 --- /dev/null +++ b/ssh_run.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# Quick SSH script launcher +# Usage: ./ssh_run.sh script_name [args...] + +if [ $# -eq 0 ]; then + echo "Available SSH scripts:" + ls -1 scripts/ssh/*.sh | sed 's/scripts\/ssh\///g' | sed 's/\.sh//g' + echo "" + echo "Usage: $0 script_name [env_vars...]" + echo "Example: $0 recon" + echo "Example: HOST=pc-74269 $0 install-thunderbird" + exit 1 +fi + +SCRIPT_NAME="$1" +shift + +cd scripts/ssh && ./$SCRIPT_NAME.sh "$@" diff --git a/winrm_run.sh b/winrm_run.sh new file mode 100755 index 0000000..92c70b9 --- /dev/null +++ b/winrm_run.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# Quick WinRM script launcher +# Usage: ./winrm_run.sh script_name [args...] + +if [ $# -eq 0 ]; then + echo "Available WinRM scripts:" + ls -1 scripts/winrm/*.py | sed 's/scripts\/winrm\///g' | sed 's/\.py//g' + echo "" + echo "Usage: $0 script_name [args...]" + echo "Example: $0 test_simple_winrm" + echo "Example: HOST=pc-74269 $0 smart_executor ../powershell/recon.ps1" + exit 1 +fi + +SCRIPT_NAME="$1" +shift + +cd scripts/winrm && python3 $SCRIPT_NAME.py "$@"