Initial commit

This commit is contained in:
2026-04-24 17:41:53 +08:00
commit 7105e8b165
94 changed files with 8141 additions and 0 deletions

77
.gitignore vendored Normal file
View File

@@ -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/

142
CLAUDE.md Normal file
View File

@@ -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
# 安裝 sshpassSSH 方法必需)
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<HASH>]` 區塊而非 `Default=1`。設置腳本正確處理這種變更。
### Win11Debloat 範圍限制
靜默模式僅將 UI 設定套用到執行使用者。使用 `apply-ui-to-user.sh` 將設定同步到其他使用者。
### WinRM 目標需求
- 目標上運行的 WinRM 服務(連接埠 5985 HTTP 或 5986 HTTPS
- 執行過 `Enable-PSRemoting -Force`
- 為 Windows 遠端管理啟用防火牆規則
- 對於 HTTPWinRM 配置中的 `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<HASH>]` 區塊配置正確
3. **Telegram Desktop 安裝限制**:不支援機器範圍安裝,使用 per-user 變通方法
4. **Win11Debloat 配置架構**:使用 `Tweaks`/`Deployment`/`Apps`,不是 `Settings`
5. **NirSoft 工具 URL 變更**:部分下載連結可能失效,腳本會跳過並繼續

22
README.md Normal file
View File

@@ -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.

View File

@@ -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"
}

View File

@@ -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)."
}

View File

@@ -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"
}
}

View File

@@ -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 }
]
}

167
docs/CLAUDE.md Normal file
View File

@@ -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<HASH>]` 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)

114
docs/FILE_ORGANIZATION.md Normal file
View File

@@ -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/ 目錄
這種組織方式讓專案更清晰、易於維護和理解!

371
docs/README.md Normal file
View File

@@ -0,0 +1,371 @@
# 192.168.88.108 Toolkit — PC-74269
遠端設定 `PC-74269`192.168.88.108Lenovo ThinkPad T480Windows 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 Server22 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 zipWin11Debloat 需要 `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、Spotlightper-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\<user>\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`
安裝 Thunderbirdwinget 先試,失敗 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<HASH>]` 區塊決定 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 版本並使用對應的 GVLKGeneric 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-<hostname>-<timestamp>.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 T48020L5S24L00
- Windows 10 Probuild 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/廣告/UIverify 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<HASH>]` 區塊決定預設 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 的 TaskbarDaTelemetry 在 `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`

View File

@@ -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 的效能
**專案現在更加專業、易用、可維護!** 🚀

View File

@@ -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**: 腳本內容嵌入 SOAPBase64 編碼
## 🛡️ 安全性比較
### 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. **完善錯誤處理和重試邏輯**

View File

@@ -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 深度整合
根據任務特性智能選擇,獲得最佳的管理體驗!

50
examples/Remote-PC.rdp Normal file
View File

@@ -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

View File

@@ -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

39
examples/map-drive-S.cmd Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"
}
}

View File

@@ -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
}

View File

@@ -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"

View File

@@ -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"

View File

@@ -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."

View File

@@ -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
}

View File

@@ -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."

View File

@@ -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)."

View File

@@ -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."

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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."

View File

@@ -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

View File

@@ -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!"

View File

@@ -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"

View File

@@ -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."

View File

@@ -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"

View File

@@ -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`""

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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."

View File

@@ -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
}

View File

@@ -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"

View File

@@ -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)"
}

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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)" }
}

View File

@@ -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

View File

@@ -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'."

View File

@@ -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"
}

View File

@@ -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:\"

View File

@@ -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"

View File

@@ -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
}

View File

@@ -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<HASH>] 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<HASH> 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."

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

28
scripts/ssh/activate-windows.sh Executable file
View File

@@ -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"

28
scripts/ssh/apply-ui-to-user.sh Executable file
View File

@@ -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"

36
scripts/ssh/check-winrm.sh Executable file
View File

@@ -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"

28
scripts/ssh/configure-ime.sh Executable file
View File

@@ -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"

View File

@@ -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"

47
scripts/ssh/enable-winrm.sh Executable file
View File

@@ -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"

View File

@@ -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"

31
scripts/ssh/fix-winrm.sh Executable file
View File

@@ -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"

30
scripts/ssh/install-apps.sh Executable file
View File

@@ -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"

27
scripts/ssh/install-cports.sh Executable file
View File

@@ -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"

29
scripts/ssh/install-nirsoft.sh Executable file
View File

@@ -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"

27
scripts/ssh/install-telegram.sh Executable file
View File

@@ -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"

View File

@@ -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"

52
scripts/ssh/nirsoft-dump.sh Executable file
View File

@@ -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)"

26
scripts/ssh/recon.sh Executable file
View File

@@ -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"

28
scripts/ssh/remove-apps.sh Executable file
View File

@@ -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"

View File

@@ -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'"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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 '<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"><soap:Header/><soap:Body><wsen:Enumerate xmlns:wsen="http://schemas.xmlsoap.org/ws/2004/09/enumeration"><wsen:Filter xmlns:wsman="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd">SELECT * FROM Win32_Service</wsen:Filter></wsen:Enumerate></soap:Body></soap:Envelope>' 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"

View File

@@ -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"

27
scripts/ssh/verify-debloat.sh Executable file
View File

@@ -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"

34
scripts/ssh/win11debloat.sh Executable file
View File

@@ -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"

View File

@@ -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()

View File

@@ -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()

61
scripts/winrm/recon-winrm.py Executable file
View File

@@ -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()

172
scripts/winrm/smart_executor.py Executable file
View File

@@ -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 <script.ps1>")
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()

View File

@@ -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")

View File

@@ -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")

View File

@@ -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(
'<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:wsmid="http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd">'
'<s:Header></s:Header>'
'<s:Body>'
'<wsmid:Identify/>'
'</s:Body>'
'</s:Envelope>'
)
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()

250
scripts/winrm/winrm_executor.py Executable file
View File

@@ -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()

View File

@@ -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()

18
ssh_run.sh Executable file
View File

@@ -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 "$@"

18
winrm_run.sh Executable file
View File

@@ -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 "$@"