Initial commit
This commit is contained in:
133
scripts/powershell/activate-windows.ps1
Normal file
133
scripts/powershell/activate-windows.ps1
Normal 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
|
||||
}
|
||||
127
scripts/powershell/add-desktop-icons-for-user.ps1
Normal file
127
scripts/powershell/add-desktop-icons-for-user.ps1
Normal 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"
|
||||
100
scripts/powershell/add-desktop-icons.ps1
Normal file
100
scripts/powershell/add-desktop-icons.ps1
Normal 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"
|
||||
77
scripts/powershell/apply-ui-to-user.ps1
Normal file
77
scripts/powershell/apply-ui-to-user.ps1
Normal 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."
|
||||
145
scripts/powershell/check-winrm.ps1
Normal file
145
scripts/powershell/check-winrm.ps1
Normal 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
|
||||
}
|
||||
119
scripts/powershell/cleanup-and-fix-winget.ps1
Normal file
119
scripts/powershell/cleanup-and-fix-winget.ps1
Normal 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."
|
||||
112
scripts/powershell/configure-ime.ps1
Normal file
112
scripts/powershell/configure-ime.ps1
Normal 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)."
|
||||
94
scripts/powershell/create-download-links.ps1
Normal file
94
scripts/powershell/create-download-links.ps1
Normal 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."
|
||||
86
scripts/powershell/debug-winrm-network.ps1
Normal file
86
scripts/powershell/debug-winrm-network.ps1
Normal 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
|
||||
83
scripts/powershell/diagnose-winget.ps1
Normal file
83
scripts/powershell/diagnose-winget.ps1
Normal 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"
|
||||
155
scripts/powershell/enable-winrm.ps1
Normal file
155
scripts/powershell/enable-winrm.ps1
Normal 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
|
||||
36
scripts/powershell/firewall-allow-ssh.ps1
Normal file
36
scripts/powershell/firewall-allow-ssh.ps1
Normal 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."
|
||||
62
scripts/powershell/fix-winrm.ps1
Normal file
62
scripts/powershell/fix-winrm.ps1
Normal 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
|
||||
122
scripts/powershell/install-apps-direct.ps1
Normal file
122
scripts/powershell/install-apps-direct.ps1
Normal 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!"
|
||||
103
scripts/powershell/install-apps-fixed.ps1
Normal file
103
scripts/powershell/install-apps-fixed.ps1
Normal 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"
|
||||
99
scripts/powershell/install-apps-winget.ps1
Normal file
99
scripts/powershell/install-apps-winget.ps1
Normal 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."
|
||||
163
scripts/powershell/install-apps.ps1
Normal file
163
scripts/powershell/install-apps.ps1
Normal 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"
|
||||
66
scripts/powershell/install-cports.ps1
Normal file
66
scripts/powershell/install-cports.ps1
Normal 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`""
|
||||
104
scripts/powershell/install-essential-apps.ps1
Normal file
104
scripts/powershell/install-essential-apps.ps1
Normal 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"
|
||||
102
scripts/powershell/install-nirsoft.ps1
Normal file
102
scripts/powershell/install-nirsoft.ps1
Normal 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"
|
||||
187
scripts/powershell/install-remaining-apps.ps1
Normal file
187
scripts/powershell/install-remaining-apps.ps1
Normal 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"
|
||||
102
scripts/powershell/install-telegram.ps1
Normal file
102
scripts/powershell/install-telegram.ps1
Normal 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."
|
||||
73
scripts/powershell/install-thunderbird.ps1
Normal file
73
scripts/powershell/install-thunderbird.ps1
Normal 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
|
||||
}
|
||||
135
scripts/powershell/investigate-failures.ps1
Normal file
135
scripts/powershell/investigate-failures.ps1
Normal 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"
|
||||
145
scripts/powershell/map-anonymous-drive.ps1
Normal file
145
scripts/powershell/map-anonymous-drive.ps1
Normal 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)"
|
||||
}
|
||||
98
scripts/powershell/map-drive-r.ps1
Normal file
98
scripts/powershell/map-drive-r.ps1
Normal 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"
|
||||
96
scripts/powershell/nirsoft-dump.ps1
Normal file
96
scripts/powershell/nirsoft-dump.ps1
Normal 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
|
||||
72
scripts/powershell/pin-rdp-to-taskbar.ps1
Normal file
72
scripts/powershell/pin-rdp-to-taskbar.ps1
Normal 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"
|
||||
91
scripts/powershell/recon.ps1
Normal file
91
scripts/powershell/recon.ps1
Normal 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
|
||||
102
scripts/powershell/remove-apps.ps1
Normal file
102
scripts/powershell/remove-apps.ps1
Normal 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)" }
|
||||
}
|
||||
82
scripts/powershell/remove-windows-ai.ps1
Normal file
82
scripts/powershell/remove-windows-ai.ps1
Normal 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
|
||||
51
scripts/powershell/rename-hide-accounts.ps1
Normal file
51
scripts/powershell/rename-hide-accounts.ps1
Normal 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'."
|
||||
97
scripts/powershell/set-user-desktop-icons.ps1
Normal file
97
scripts/powershell/set-user-desktop-icons.ps1
Normal 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"
|
||||
}
|
||||
138
scripts/powershell/setup-network-drive.ps1
Normal file
138
scripts/powershell/setup-network-drive.ps1
Normal 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:\"
|
||||
102
scripts/powershell/setup-network-share.ps1
Normal file
102
scripts/powershell/setup-network-share.ps1
Normal 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"
|
||||
89
scripts/powershell/setup-rdp-with-credentials.ps1
Normal file
89
scripts/powershell/setup-rdp-with-credentials.ps1
Normal 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
|
||||
}
|
||||
150
scripts/powershell/setup-thunderbird.ps1
Normal file
150
scripts/powershell/setup-thunderbird.ps1
Normal 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."
|
||||
111
scripts/powershell/verify-ai-removed.ps1
Normal file
111
scripts/powershell/verify-ai-removed.ps1
Normal 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)
|
||||
106
scripts/powershell/verify-debloat.ps1
Normal file
106
scripts/powershell/verify-debloat.ps1
Normal 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)
|
||||
91
scripts/powershell/win11debloat.ps1
Normal file
91
scripts/powershell/win11debloat.ps1
Normal 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
|
||||
Reference in New Issue
Block a user