Add maintenance toolkit, DNC/OnGuard utilities
- Add Invoke-RemoteMaintenance.ps1: Remote maintenance tasks (DISM, SFC, disk cleanup, etc.) - Add DNC/, dncfix/, edncfix/: DNC configuration utilities - Add onguard/: OnGuard integration scripts - Add tools/: Additional utility scripts - Update remote-execution/README.md with maintenance toolkit docs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
12
DNC/update_dnc.bat
Normal file
12
DNC/update_dnc.bat
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
@echo off
|
||||||
|
echo Updating DNC files...
|
||||||
|
|
||||||
|
echo Stopping DncMain.exe if running...
|
||||||
|
taskkill /F /IM DncMain.exe 2>nul
|
||||||
|
|
||||||
|
copy /Y "S:\DT\DNC\DNCdll.dll" "C:\Program Files (x86)\dnc\bin\"
|
||||||
|
copy /Y "S:\DT\DNC\DncMain.exe" "C:\Program Files (x86)\dnc\bin\"
|
||||||
|
copy /Y "S:\DT\DNC\mxTransactionDll.dll" "C:\Program Files (x86)\dnc\bin\"
|
||||||
|
|
||||||
|
echo Done.
|
||||||
|
pause
|
||||||
3
dncfix/fixvtm.bat
Normal file
3
dncfix/fixvtm.bat
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@echo off
|
||||||
|
powershell.exe -ExecutionPolicy Bypass -File ".\vtmfix.ps1"
|
||||||
|
pause
|
||||||
64
dncfix/vtmfix.ps1
Normal file
64
dncfix/vtmfix.ps1
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# Real-time file watcher to strip 0xFF from .pun files
|
||||||
|
# Watches folder and cleans files as soon as they're created/modified
|
||||||
|
|
||||||
|
$watchFolder = "C:\Dnc_Files\Q" # Change to your DNC program folder
|
||||||
|
$fileFilter = "*.pun" # Watch .pun files
|
||||||
|
|
||||||
|
Write-Host "Watching $watchFolder for new/modified $fileFilter files..."
|
||||||
|
Write-Host "Press Ctrl+C to stop"
|
||||||
|
|
||||||
|
# Create file system watcher
|
||||||
|
$watcher = New-Object System.IO.FileSystemWatcher
|
||||||
|
$watcher.Path = $watchFolder
|
||||||
|
$watcher.Filter = $fileFilter
|
||||||
|
$watcher.IncludeSubdirectories = $true
|
||||||
|
$watcher.EnableRaisingEvents = $true
|
||||||
|
|
||||||
|
# Define what to do when file is created or changed
|
||||||
|
$action = {
|
||||||
|
$path = $Event.SourceEventArgs.FullPath
|
||||||
|
$changeType = $Event.SourceEventArgs.ChangeType
|
||||||
|
|
||||||
|
Write-Host "$(Get-Date -Format 'HH:mm:ss') - $changeType detected: $path"
|
||||||
|
|
||||||
|
# Wait a moment for file to finish writing
|
||||||
|
Start-Sleep -Milliseconds 500
|
||||||
|
|
||||||
|
try {
|
||||||
|
# Read file as bytes
|
||||||
|
$bytes = [System.IO.File]::ReadAllBytes($path)
|
||||||
|
$originalCount = $bytes.Count
|
||||||
|
|
||||||
|
# Remove all 0xFF bytes
|
||||||
|
$cleaned = $bytes | Where-Object { $_ -ne 255 }
|
||||||
|
$newCount = $cleaned.Count
|
||||||
|
|
||||||
|
# Only rewrite if we found 0xFF
|
||||||
|
if ($originalCount -ne $newCount) {
|
||||||
|
[System.IO.File]::WriteAllBytes($path, $cleaned)
|
||||||
|
$removed = $originalCount - $newCount
|
||||||
|
Write-Host " [OK] Cleaned! Removed $removed byte(s) [0xFF]" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host " [SKIP] No 0xFF found, file OK" -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Host " [ERROR] $_" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Register event handlers
|
||||||
|
Register-ObjectEvent -InputObject $watcher -EventName Created -Action $action
|
||||||
|
Register-ObjectEvent -InputObject $watcher -EventName Changed -Action $action
|
||||||
|
|
||||||
|
# Keep script running
|
||||||
|
try {
|
||||||
|
while ($true) {
|
||||||
|
Start-Sleep -Seconds 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
# Cleanup on exit
|
||||||
|
$watcher.Dispose()
|
||||||
|
Write-Host "`nStopped watching folder"
|
||||||
|
}
|
||||||
1
edncfix
Submodule
1
edncfix
Submodule
Submodule edncfix added at 28641c47c5
93
onguard/silentInstall.ps1
Normal file
93
onguard/silentInstall.ps1
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#Requires -RunAsAdministrator
|
||||||
|
|
||||||
|
param(
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$True)]
|
||||||
|
[string]
|
||||||
|
$LicenseServer,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$True)]
|
||||||
|
[string]
|
||||||
|
$DatabaseServer
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
|
||||||
|
|
||||||
|
# Only Client installations are supported at this time
|
||||||
|
# $Features can optionally be assigned to an array of specific Client Features from within available features
|
||||||
|
# The available client features are: AlarmMonitoring AreaAccessManager BadgeDesigner DeviceDicoveryService DeviceDiscovery FormsDesigner IDCredentialCenter MapDesigner SystemAdministration VideoViewer VisitorManagement
|
||||||
|
# example: $Features = @("AlarmMonitoring","IDCredentialCenter","SystemAdministration")
|
||||||
|
|
||||||
|
$Features = "Client"
|
||||||
|
|
||||||
|
$availableFeatures = @("AlarmMonitoring","AreaAccessManager","BadgeDesigner","DeviceDicoveryService","DeviceDiscovery","FormsDesigner","IDCredentialCenter","MapDesigner","SystemAdministration","VideoViewer","VisitorManagement","ApplicationServer","ClientUpdateServer","CommunicationServer","DataConduITService","DataExchangeServer","EnterpriseAdministration","GlobalOutputServer","IDAllocationService","Import","LicenseSystemServer","LoginDriver","OpenAccess","Replicator","ReportsDashboard","SetupDB","UniversalTimeConversionUtility","VideoArchiveServer")
|
||||||
|
|
||||||
|
# Formats the feature list parameter based on the installation type or features passed in
|
||||||
|
if ( "Client" -in $Features ) {
|
||||||
|
Write-Host "Installing client features silently."
|
||||||
|
$featureParam = "ADDLOCAL=""AlarmMonitoring,AreaAccessManager,BadgeDesigner,DeviceDicoveryService,DeviceDiscovery,FormsDesigner,IDCredentialCenter,MapDesigner,SystemAdministration,VideoViewer,VisitorManagement"" REMOVE=""ApplicationServer,ClientUpdateServer,CommunicationServer,DataConduITService,DataExchangeServer,EnterpriseAdministration,GlobalOutputServer,IDAllocationService,Import,LicenseSystemServer,LoginDriver,OpenAccess,Replicator,ReportsDashboard,SetupDB,UniversalTimeConversionUtility,VideoArchiveServer"""
|
||||||
|
Write-Host $featureParam
|
||||||
|
}
|
||||||
|
elseif ( -not @($Features | where {$availableFeatures -notcontains $_}).Count ) {
|
||||||
|
# Ensures any arguments are in the available features
|
||||||
|
# Features can optionabe an array of available Client Features Only
|
||||||
|
Write-Host "Installing a custom set of features."
|
||||||
|
$removeFeatures = @()
|
||||||
|
foreach ($feature in $availableFeatures) {
|
||||||
|
if ($args -notcontains $feature) {
|
||||||
|
$removeFeatures += $feature
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$featureParam = "ADDLOCAL=""$($args -join ",")"" REMOVE=""$($removeFeatures -join ",")"""
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Throw "An error was encountered with the arguments"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( test-path 'HKLM:\SOFTWARE\WOW6432Node\Lenel\OnGuard') {
|
||||||
|
$productCode = Get-ItemProperty -Path Registry::HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Lenel\OnGuard -Name "ProductCode"
|
||||||
|
$systemtypedetect="PREV101102=""102"""
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$systemtypedetect="SYSTEMTYPE=""C"""
|
||||||
|
}
|
||||||
|
|
||||||
|
# When an existing instance is installed: backup the registry, remove the product, restore the registry values for the reinstall to pickup
|
||||||
|
If ( $productCode ) {
|
||||||
|
Md Registry::HKLM\SOFTWARE\WOW6432Node\LenelBackup -Force
|
||||||
|
Copy-Item -Path Registry::HKLM\SOFTWARE\WOW6432Node\Lenel\OnGuard -Destination Registry::HKLM\SOFTWARE\WOW6432Node\LenelBackup -Recurse
|
||||||
|
$Og_productCode=$productCode.ProductCode.ToString()
|
||||||
|
cmd /c msiexec.exe /x $Og_productCode /qn
|
||||||
|
Write-Host $productCode.ProductCode
|
||||||
|
Copy-Item -Path Registry::HKLM\SOFTWARE\WOW6432Node\LenelBackup\OnGuard -Destination Registry::HKLM\SOFTWARE\WOW6432Node\Lenel -Recurse
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentpath = (Get-Item -Path ".\").FullName
|
||||||
|
|
||||||
|
# Install Microsoft Direct X for Managed Code
|
||||||
|
Write-Host "Installing Direct X for Managed Code..."
|
||||||
|
cmd /c msiexec.exe /i "$($currentpath)\Windows\Temp\DXManaged\mdxredist.msi" /qn
|
||||||
|
|
||||||
|
# Install Acuant Capture/Scanning SDK
|
||||||
|
Write-Host "Install Acuant Capture/Scanning SDK..."
|
||||||
|
cmd /c "$($currentpath)\Windows\Temp\CSSN_SDK\sdk_setup_is.exe" /s /a /s /f1 "$($currentpath)\Windows\Temp\CSSN_SDK\setup.iss"
|
||||||
|
|
||||||
|
# Install Crystal Reports Runtime
|
||||||
|
Write-Host "Installing Crystal Reports Runtime"
|
||||||
|
cmd /c msiexec.exe /i "$($currentpath)\Windows\Temp\Crystal\CRRuntime_32bit_13_0_32.msi" /qn UPGRADE=1
|
||||||
|
|
||||||
|
# Install Microsoft Windows Media Encoder
|
||||||
|
Write-Host "Installing Microsoft Windows Media Encoder"
|
||||||
|
cmd /c msiexec.exe /i "$($currentpath)\Windows\Temp\WMEncoder\WMEncoder.msi" /qn
|
||||||
|
|
||||||
|
# Install UltrView SDK
|
||||||
|
Write-Host "Installing UltrView SDK"
|
||||||
|
cmd /c "$($currentpath)\Windows\Temp\UltraView\UltraViewSoftwareDevelopmentKit.exe" -silent
|
||||||
|
|
||||||
|
# Setting environmental variables
|
||||||
|
cmd /c setx /M PATH "%PATH%;C:\Program Files (x86)\Common Files\Lenel;C:\Program Files (x86)\Acuant\SDK" | Out-Null
|
||||||
|
|
||||||
|
# Install OnGuard
|
||||||
|
Write-Host "Installing OnGuard"
|
||||||
|
cmd /c "$($currentpath)\setup.exe" /s /v"/qn /L*V "$($env:LOCALAPPDATA)\OnGuardSetup.log" $systemtypedetect LICENSESERVER=$LicenseServer DSN=$DatabaseServer DATABASETYPE="SQL" REBOOT=Suppress $featureParam"
|
||||||
836
remote-execution/Invoke-RemoteMaintenance.ps1
Normal file
836
remote-execution/Invoke-RemoteMaintenance.ps1
Normal file
@@ -0,0 +1,836 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Remote maintenance toolkit for shopfloor PCs via WinRM.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Executes maintenance tasks on remote shopfloor PCs using WinRM.
|
||||||
|
Supports system repair, disk optimization, cleanup, and database updates.
|
||||||
|
|
||||||
|
.PARAMETER ComputerName
|
||||||
|
Single computer name, IP address, or array of computers to target.
|
||||||
|
|
||||||
|
.PARAMETER ComputerListFile
|
||||||
|
Path to text file containing computer names/IPs (one per line).
|
||||||
|
|
||||||
|
.PARAMETER All
|
||||||
|
Target all shopfloor PCs from ShopDB database.
|
||||||
|
|
||||||
|
.PARAMETER Task
|
||||||
|
Maintenance task to execute. Available tasks:
|
||||||
|
|
||||||
|
REPAIR:
|
||||||
|
- DISM : Run DISM /Online /Cleanup-Image /RestoreHealth
|
||||||
|
- SFC : Run SFC /scannow (System File Checker)
|
||||||
|
|
||||||
|
OPTIMIZATION:
|
||||||
|
- OptimizeDisk : TRIM for SSD, Defrag for HDD
|
||||||
|
- DiskCleanup : Windows Disk Cleanup (temp files, updates)
|
||||||
|
- ClearUpdateCache : Clear Windows Update cache (fixes stuck updates)
|
||||||
|
- ClearBrowserCache: Clear Chrome/Edge cache files
|
||||||
|
|
||||||
|
SERVICES:
|
||||||
|
- RestartSpooler : Restart Print Spooler service
|
||||||
|
- FlushDNS : Clear DNS resolver cache
|
||||||
|
- RestartWinRM : Restart WinRM service
|
||||||
|
|
||||||
|
TIME/DATE:
|
||||||
|
- SetTimezone : Set timezone to Eastern Standard Time
|
||||||
|
- SyncTime : Force time sync with domain controller
|
||||||
|
|
||||||
|
.PARAMETER Credential
|
||||||
|
PSCredential for remote authentication. Prompts if not provided.
|
||||||
|
|
||||||
|
.PARAMETER ApiUrl
|
||||||
|
ShopDB API URL for database updates.
|
||||||
|
|
||||||
|
.PARAMETER ThrottleLimit
|
||||||
|
Maximum concurrent remote sessions (default: 5).
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
# Run DISM on a single PC
|
||||||
|
.\Invoke-RemoteMaintenance.ps1 -ComputerName "G1ZTNCX3ESF" -Task DISM
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
# Optimize disks on multiple PCs
|
||||||
|
.\Invoke-RemoteMaintenance.ps1 -ComputerName "PC01","PC02" -Task OptimizeDisk
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
# Run disk cleanup on all shopfloor PCs
|
||||||
|
.\Invoke-RemoteMaintenance.ps1 -All -Task DiskCleanup
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
# Update database with disk health info
|
||||||
|
.\Invoke-RemoteMaintenance.ps1 -All -Task DiskHealth
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
# Run all database update tasks
|
||||||
|
.\Invoke-RemoteMaintenance.ps1 -ComputerName "PC01" -Task AllDatabaseUpdates
|
||||||
|
|
||||||
|
.NOTES
|
||||||
|
Author: Shop Floor Tools
|
||||||
|
Date: 2025-12-26
|
||||||
|
Requires: PowerShell 5.1+, WinRM enabled on targets, Admin credentials
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding(DefaultParameterSetName='ByName')]
|
||||||
|
param(
|
||||||
|
[Parameter(ParameterSetName='ByName', Position=0)]
|
||||||
|
[string[]]$ComputerName,
|
||||||
|
|
||||||
|
[Parameter(ParameterSetName='ByFile')]
|
||||||
|
[string]$ComputerListFile,
|
||||||
|
|
||||||
|
[Parameter(ParameterSetName='All')]
|
||||||
|
[switch]$All,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[ValidateSet(
|
||||||
|
'DISM', 'SFC', 'OptimizeDisk', 'DiskCleanup', 'ClearUpdateCache',
|
||||||
|
'RestartSpooler', 'FlushDNS', 'ClearBrowserCache', 'RestartWinRM',
|
||||||
|
'SetTimezone', 'SyncTime'
|
||||||
|
)]
|
||||||
|
[string]$Task,
|
||||||
|
|
||||||
|
[Parameter()]
|
||||||
|
[PSCredential]$Credential,
|
||||||
|
|
||||||
|
[Parameter()]
|
||||||
|
[string]$ApiUrl = "https://tsgwp00525.rd.ds.ge.com/shopdb/api.asp",
|
||||||
|
|
||||||
|
[Parameter()]
|
||||||
|
[string]$DnsSuffix = "logon.ds.ge.com",
|
||||||
|
|
||||||
|
[Parameter()]
|
||||||
|
[int]$ThrottleLimit = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SSL/TLS Configuration
|
||||||
|
# =============================================================================
|
||||||
|
try {
|
||||||
|
if (-not ([System.Management.Automation.PSTypeName]'TrustAllCertsPolicy').Type) {
|
||||||
|
Add-Type @"
|
||||||
|
using System.Net;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
public class TrustAllCertsPolicy : ICertificatePolicy {
|
||||||
|
public bool CheckValidationResult(
|
||||||
|
ServicePoint srvPoint, X509Certificate certificate,
|
||||||
|
WebRequest request, int certificateProblem) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"@
|
||||||
|
}
|
||||||
|
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
|
||||||
|
} catch { }
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Helper Functions
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||||
|
$color = switch ($Level) {
|
||||||
|
"ERROR" { "Red" }
|
||||||
|
"WARNING" { "Yellow" }
|
||||||
|
"SUCCESS" { "Green" }
|
||||||
|
"TASK" { "Cyan" }
|
||||||
|
default { "White" }
|
||||||
|
}
|
||||||
|
Write-Host "[$timestamp] [$Level] $Message" -ForegroundColor $color
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-ShopfloorPCsFromApi {
|
||||||
|
param([string]$ApiUrl)
|
||||||
|
try {
|
||||||
|
$response = Invoke-RestMethod -Uri "$ApiUrl`?action=getShopfloorPCs" -Method Get -ErrorAction Stop
|
||||||
|
if ($response.success -and $response.data) {
|
||||||
|
return $response.data
|
||||||
|
}
|
||||||
|
return @()
|
||||||
|
} catch {
|
||||||
|
Write-Log "Failed to query API: $_" -Level "ERROR"
|
||||||
|
return @()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Maintenance Task Scriptblocks
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
$TaskScripts = @{
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# DISM - Deployment Image Servicing and Management
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
'DISM' = {
|
||||||
|
$result = @{
|
||||||
|
Success = $false
|
||||||
|
Task = 'DISM'
|
||||||
|
Hostname = $env:COMPUTERNAME
|
||||||
|
Output = ""
|
||||||
|
Error = $null
|
||||||
|
StartTime = Get-Date
|
||||||
|
Duration = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Write-Output "Starting DISM /Online /Cleanup-Image /RestoreHealth..."
|
||||||
|
Write-Output "This may take 10-30 minutes..."
|
||||||
|
|
||||||
|
$dismResult = & dism.exe /Online /Cleanup-Image /RestoreHealth 2>&1
|
||||||
|
$result.Output = $dismResult -join "`n"
|
||||||
|
$result.ExitCode = $LASTEXITCODE
|
||||||
|
$result.Success = ($LASTEXITCODE -eq 0)
|
||||||
|
|
||||||
|
if ($result.Success) {
|
||||||
|
Write-Output "DISM completed successfully."
|
||||||
|
} else {
|
||||||
|
Write-Output "DISM completed with exit code: $LASTEXITCODE"
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
$result.Error = $_.Exception.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
$result.EndTime = Get-Date
|
||||||
|
$result.Duration = [math]::Round(((Get-Date) - $result.StartTime).TotalMinutes, 2)
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# SFC - System File Checker
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
'SFC' = {
|
||||||
|
$result = @{
|
||||||
|
Success = $false
|
||||||
|
Task = 'SFC'
|
||||||
|
Hostname = $env:COMPUTERNAME
|
||||||
|
Output = ""
|
||||||
|
Error = $null
|
||||||
|
StartTime = Get-Date
|
||||||
|
Duration = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Write-Output "Starting SFC /scannow..."
|
||||||
|
Write-Output "This may take 10-20 minutes..."
|
||||||
|
|
||||||
|
$sfcResult = & sfc.exe /scannow 2>&1
|
||||||
|
$result.Output = $sfcResult -join "`n"
|
||||||
|
$result.ExitCode = $LASTEXITCODE
|
||||||
|
|
||||||
|
# SFC exit codes: 0 = no issues, 1 = issues found and fixed
|
||||||
|
$result.Success = ($LASTEXITCODE -eq 0 -or $LASTEXITCODE -eq 1)
|
||||||
|
|
||||||
|
# Parse output for summary
|
||||||
|
if ($result.Output -match "found corrupt files and successfully repaired") {
|
||||||
|
$result.Summary = "Corrupt files found and repaired"
|
||||||
|
} elseif ($result.Output -match "did not find any integrity violations") {
|
||||||
|
$result.Summary = "No integrity violations found"
|
||||||
|
} elseif ($result.Output -match "found corrupt files but was unable to fix") {
|
||||||
|
$result.Summary = "Corrupt files found but could not be repaired"
|
||||||
|
$result.Success = $false
|
||||||
|
} else {
|
||||||
|
$result.Summary = "Scan completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
$result.Error = $_.Exception.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
$result.EndTime = Get-Date
|
||||||
|
$result.Duration = [math]::Round(((Get-Date) - $result.StartTime).TotalMinutes, 2)
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# OptimizeDisk - TRIM for SSD, Defrag for HDD
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
'OptimizeDisk' = {
|
||||||
|
$result = @{
|
||||||
|
Success = $false
|
||||||
|
Task = 'OptimizeDisk'
|
||||||
|
Hostname = $env:COMPUTERNAME
|
||||||
|
Output = ""
|
||||||
|
Error = $null
|
||||||
|
Drives = @()
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
# Get all fixed drives
|
||||||
|
$volumes = Get-Volume | Where-Object { $_.DriveType -eq 'Fixed' -and $_.DriveLetter }
|
||||||
|
|
||||||
|
foreach ($vol in $volumes) {
|
||||||
|
$driveLetter = $vol.DriveLetter
|
||||||
|
$driveResult = @{
|
||||||
|
DriveLetter = $driveLetter
|
||||||
|
Success = $false
|
||||||
|
MediaType = "Unknown"
|
||||||
|
Action = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Detect if SSD or HDD
|
||||||
|
$physicalDisk = Get-PhysicalDisk | Where-Object {
|
||||||
|
$diskNum = (Get-Partition -DriveLetter $driveLetter -ErrorAction SilentlyContinue).DiskNumber
|
||||||
|
$_.DeviceId -eq $diskNum
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($physicalDisk) {
|
||||||
|
$driveResult.MediaType = $physicalDisk.MediaType
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output "Optimizing drive ${driveLetter}: ($($driveResult.MediaType))..."
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($driveResult.MediaType -eq 'SSD') {
|
||||||
|
# TRIM for SSD
|
||||||
|
Optimize-Volume -DriveLetter $driveLetter -ReTrim -ErrorAction Stop
|
||||||
|
$driveResult.Action = "TRIM"
|
||||||
|
} else {
|
||||||
|
# Defrag for HDD
|
||||||
|
Optimize-Volume -DriveLetter $driveLetter -Defrag -ErrorAction Stop
|
||||||
|
$driveResult.Action = "Defrag"
|
||||||
|
}
|
||||||
|
$driveResult.Success = $true
|
||||||
|
Write-Output " ${driveLetter}: $($driveResult.Action) completed"
|
||||||
|
} catch {
|
||||||
|
$driveResult.Error = $_.Exception.Message
|
||||||
|
Write-Output " ${driveLetter}: Failed - $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$result.Drives += $driveResult
|
||||||
|
}
|
||||||
|
|
||||||
|
$result.Success = ($result.Drives | Where-Object { $_.Success }).Count -gt 0
|
||||||
|
$result.Output = "Optimized $($result.Drives.Count) drive(s)"
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
$result.Error = $_.Exception.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# DiskCleanup - Windows Disk Cleanup
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
'DiskCleanup' = {
|
||||||
|
$result = @{
|
||||||
|
Success = $false
|
||||||
|
Task = 'DiskCleanup'
|
||||||
|
Hostname = $env:COMPUTERNAME
|
||||||
|
Output = ""
|
||||||
|
Error = $null
|
||||||
|
SpaceFreed = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
# Get initial free space
|
||||||
|
$initialFree = (Get-PSDrive C).Free
|
||||||
|
|
||||||
|
Write-Output "Running Disk Cleanup..."
|
||||||
|
|
||||||
|
# Set cleanup flags in registry for automated cleanup
|
||||||
|
$cleanupPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches"
|
||||||
|
$categories = @(
|
||||||
|
"Temporary Files",
|
||||||
|
"Temporary Setup Files",
|
||||||
|
"Old ChkDsk Files",
|
||||||
|
"Setup Log Files",
|
||||||
|
"Windows Update Cleanup",
|
||||||
|
"Windows Defender",
|
||||||
|
"Thumbnail Cache",
|
||||||
|
"Recycle Bin"
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach ($cat in $categories) {
|
||||||
|
$catPath = Join-Path $cleanupPath $cat
|
||||||
|
if (Test-Path $catPath) {
|
||||||
|
Set-ItemProperty -Path $catPath -Name "StateFlags0100" -Value 2 -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run cleanmgr with sagerun
|
||||||
|
$cleanupProcess = Start-Process -FilePath "cleanmgr.exe" -ArgumentList "/sagerun:100" -Wait -PassThru -WindowStyle Hidden
|
||||||
|
|
||||||
|
# Also clear temp folders directly
|
||||||
|
$tempPaths = @(
|
||||||
|
"$env:TEMP",
|
||||||
|
"$env:SystemRoot\Temp",
|
||||||
|
"$env:SystemRoot\Prefetch"
|
||||||
|
)
|
||||||
|
|
||||||
|
$filesDeleted = 0
|
||||||
|
foreach ($path in $tempPaths) {
|
||||||
|
if (Test-Path $path) {
|
||||||
|
$files = Get-ChildItem -Path $path -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
foreach ($file in $files) {
|
||||||
|
try {
|
||||||
|
Remove-Item $file.FullName -Force -Recurse -ErrorAction SilentlyContinue
|
||||||
|
$filesDeleted++
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Calculate space freed
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
$finalFree = (Get-PSDrive C).Free
|
||||||
|
$result.SpaceFreed = [math]::Round(($finalFree - $initialFree) / 1GB, 2)
|
||||||
|
|
||||||
|
$result.Success = $true
|
||||||
|
$result.Output = "Cleanup completed. Space freed: $($result.SpaceFreed) GB. Temp files deleted: $filesDeleted"
|
||||||
|
Write-Output $result.Output
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
$result.Error = $_.Exception.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# ClearUpdateCache - Clear Windows Update cache
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
'ClearUpdateCache' = {
|
||||||
|
$result = @{
|
||||||
|
Success = $false
|
||||||
|
Task = 'ClearUpdateCache'
|
||||||
|
Hostname = $env:COMPUTERNAME
|
||||||
|
Output = ""
|
||||||
|
Error = $null
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Write-Output "Stopping Windows Update service..."
|
||||||
|
Stop-Service -Name wuauserv -Force -ErrorAction SilentlyContinue
|
||||||
|
Stop-Service -Name bits -Force -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
|
||||||
|
Write-Output "Clearing SoftwareDistribution folder..."
|
||||||
|
$swDistPath = "$env:SystemRoot\SoftwareDistribution"
|
||||||
|
if (Test-Path $swDistPath) {
|
||||||
|
Remove-Item "$swDistPath\*" -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output "Clearing catroot2 folder..."
|
||||||
|
$catroot2Path = "$env:SystemRoot\System32\catroot2"
|
||||||
|
if (Test-Path $catroot2Path) {
|
||||||
|
Remove-Item "$catroot2Path\*" -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output "Starting Windows Update service..."
|
||||||
|
Start-Service -Name wuauserv -ErrorAction SilentlyContinue
|
||||||
|
Start-Service -Name bits -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
$result.Success = $true
|
||||||
|
$result.Output = "Windows Update cache cleared successfully"
|
||||||
|
Write-Output $result.Output
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
$result.Error = $_.Exception.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# RestartSpooler - Restart Print Spooler service
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
'RestartSpooler' = {
|
||||||
|
$result = @{
|
||||||
|
Success = $false
|
||||||
|
Task = 'RestartSpooler'
|
||||||
|
Hostname = $env:COMPUTERNAME
|
||||||
|
Output = ""
|
||||||
|
Error = $null
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Write-Output "Stopping Print Spooler..."
|
||||||
|
Stop-Service -Name Spooler -Force -ErrorAction Stop
|
||||||
|
|
||||||
|
# Clear print queue
|
||||||
|
$printQueuePath = "$env:SystemRoot\System32\spool\PRINTERS"
|
||||||
|
if (Test-Path $printQueuePath) {
|
||||||
|
Remove-Item "$printQueuePath\*" -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output "Starting Print Spooler..."
|
||||||
|
Start-Service -Name Spooler -ErrorAction Stop
|
||||||
|
|
||||||
|
$spoolerStatus = (Get-Service -Name Spooler).Status
|
||||||
|
$result.Success = ($spoolerStatus -eq 'Running')
|
||||||
|
$result.Output = "Print Spooler restarted. Status: $spoolerStatus"
|
||||||
|
Write-Output $result.Output
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
$result.Error = $_.Exception.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# FlushDNS - Clear DNS resolver cache
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
'FlushDNS' = {
|
||||||
|
$result = @{
|
||||||
|
Success = $false
|
||||||
|
Task = 'FlushDNS'
|
||||||
|
Hostname = $env:COMPUTERNAME
|
||||||
|
Output = ""
|
||||||
|
Error = $null
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Write-Output "Flushing DNS cache..."
|
||||||
|
$flushResult = & ipconfig /flushdns 2>&1
|
||||||
|
$result.Output = $flushResult -join "`n"
|
||||||
|
$result.Success = ($LASTEXITCODE -eq 0)
|
||||||
|
Write-Output "DNS cache flushed successfully"
|
||||||
|
} catch {
|
||||||
|
$result.Error = $_.Exception.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# ClearBrowserCache - Clear Chrome/Edge cache
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
'ClearBrowserCache' = {
|
||||||
|
$result = @{
|
||||||
|
Success = $false
|
||||||
|
Task = 'ClearBrowserCache'
|
||||||
|
Hostname = $env:COMPUTERNAME
|
||||||
|
Output = ""
|
||||||
|
Error = $null
|
||||||
|
BrowsersCleared = @()
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
# Get all user profiles
|
||||||
|
$userProfiles = Get-ChildItem "C:\Users" -Directory | Where-Object { $_.Name -notin @('Public', 'Default', 'Default User') }
|
||||||
|
|
||||||
|
foreach ($profile in $userProfiles) {
|
||||||
|
$userName = $profile.Name
|
||||||
|
|
||||||
|
# Chrome cache paths
|
||||||
|
$chromeCachePaths = @(
|
||||||
|
"$($profile.FullName)\AppData\Local\Google\Chrome\User Data\Default\Cache",
|
||||||
|
"$($profile.FullName)\AppData\Local\Google\Chrome\User Data\Default\Code Cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Edge cache paths
|
||||||
|
$edgeCachePaths = @(
|
||||||
|
"$($profile.FullName)\AppData\Local\Microsoft\Edge\User Data\Default\Cache",
|
||||||
|
"$($profile.FullName)\AppData\Local\Microsoft\Edge\User Data\Default\Code Cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
$allPaths = $chromeCachePaths + $edgeCachePaths
|
||||||
|
|
||||||
|
foreach ($cachePath in $allPaths) {
|
||||||
|
if (Test-Path $cachePath) {
|
||||||
|
try {
|
||||||
|
Remove-Item "$cachePath\*" -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
$browserType = if ($cachePath -like "*Chrome*") { "Chrome" } else { "Edge" }
|
||||||
|
$result.BrowsersCleared += "$userName-$browserType"
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$result.Success = $true
|
||||||
|
$result.Output = "Cleared cache for: $($result.BrowsersCleared -join ', ')"
|
||||||
|
Write-Output $result.Output
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
$result.Error = $_.Exception.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# RestartWinRM - Restart WinRM service
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
'RestartWinRM' = {
|
||||||
|
$result = @{
|
||||||
|
Success = $false
|
||||||
|
Task = 'RestartWinRM'
|
||||||
|
Hostname = $env:COMPUTERNAME
|
||||||
|
Output = ""
|
||||||
|
Error = $null
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Write-Output "Restarting WinRM service..."
|
||||||
|
|
||||||
|
Restart-Service -Name WinRM -Force -ErrorAction Stop
|
||||||
|
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
|
||||||
|
$winrmStatus = (Get-Service -Name WinRM).Status
|
||||||
|
$result.Success = ($winrmStatus -eq 'Running')
|
||||||
|
$result.Output = "WinRM service restarted. Status: $winrmStatus"
|
||||||
|
Write-Output $result.Output
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
$result.Error = $_.Exception.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# SetTimezone - Set timezone to Eastern Standard Time
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
'SetTimezone' = {
|
||||||
|
$result = @{
|
||||||
|
Success = $false
|
||||||
|
Task = 'SetTimezone'
|
||||||
|
Hostname = $env:COMPUTERNAME
|
||||||
|
Output = ""
|
||||||
|
Error = $null
|
||||||
|
PreviousTimezone = ""
|
||||||
|
NewTimezone = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
# Get current timezone
|
||||||
|
$currentTz = Get-TimeZone
|
||||||
|
$result.PreviousTimezone = $currentTz.Id
|
||||||
|
|
||||||
|
$targetTz = "Eastern Standard Time"
|
||||||
|
|
||||||
|
if ($currentTz.Id -eq $targetTz) {
|
||||||
|
$result.Success = $true
|
||||||
|
$result.NewTimezone = $currentTz.Id
|
||||||
|
$result.Output = "Timezone already set to $targetTz"
|
||||||
|
Write-Output $result.Output
|
||||||
|
} else {
|
||||||
|
Write-Output "Changing timezone from $($currentTz.Id) to $targetTz..."
|
||||||
|
|
||||||
|
Set-TimeZone -Id $targetTz -ErrorAction Stop
|
||||||
|
|
||||||
|
# Verify change
|
||||||
|
$newTz = Get-TimeZone
|
||||||
|
$result.NewTimezone = $newTz.Id
|
||||||
|
$result.Success = ($newTz.Id -eq $targetTz)
|
||||||
|
$result.Output = "Timezone changed: $($currentTz.Id) -> $($newTz.Id)"
|
||||||
|
Write-Output $result.Output
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
$result.Error = $_.Exception.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# SyncTime - Sync time with domain controller
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
'SyncTime' = {
|
||||||
|
$result = @{
|
||||||
|
Success = $false
|
||||||
|
Task = 'SyncTime'
|
||||||
|
Hostname = $env:COMPUTERNAME
|
||||||
|
Output = ""
|
||||||
|
Error = $null
|
||||||
|
TimeBefore = ""
|
||||||
|
TimeAfter = ""
|
||||||
|
TimeSource = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result.TimeBefore = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
|
||||||
|
|
||||||
|
Write-Output "Syncing time with domain controller..."
|
||||||
|
|
||||||
|
# Get current time source
|
||||||
|
$w32tmSource = & w32tm /query /source 2>&1
|
||||||
|
$result.TimeSource = ($w32tmSource -join " ").Trim()
|
||||||
|
|
||||||
|
# Force time resync
|
||||||
|
$resyncResult = & w32tm /resync /force 2>&1
|
||||||
|
$resyncOutput = $resyncResult -join "`n"
|
||||||
|
|
||||||
|
# Check if successful
|
||||||
|
if ($resyncOutput -match "The command completed successfully" -or $LASTEXITCODE -eq 0) {
|
||||||
|
Start-Sleep -Seconds 1
|
||||||
|
$result.TimeAfter = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
|
||||||
|
$result.Success = $true
|
||||||
|
$result.Output = "Time synced with $($result.TimeSource). Time: $($result.TimeAfter)"
|
||||||
|
} else {
|
||||||
|
$result.Output = "Sync attempted. Result: $resyncOutput"
|
||||||
|
$result.Success = $false
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output $result.Output
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
$result.Error = $_.Exception.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Main Execution
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "=" * 70 -ForegroundColor Cyan
|
||||||
|
Write-Host " Remote Maintenance Tool - Task: $Task" -ForegroundColor Cyan
|
||||||
|
Write-Host "=" * 70 -ForegroundColor Cyan
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# Get credentials
|
||||||
|
if (-not $Credential) {
|
||||||
|
Write-Log "Enter credentials for remote PCs:" -Level "INFO"
|
||||||
|
$Credential = Get-Credential -Message "Enter admin credentials for remote PCs"
|
||||||
|
if (-not $Credential) {
|
||||||
|
Write-Log "Credentials required. Exiting." -Level "ERROR"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build computer list
|
||||||
|
$computers = @()
|
||||||
|
|
||||||
|
if ($All) {
|
||||||
|
Write-Log "Querying ShopDB for all shopfloor PCs..." -Level "INFO"
|
||||||
|
$shopfloorPCs = Get-ShopfloorPCsFromApi -ApiUrl $ApiUrl
|
||||||
|
$computers = $shopfloorPCs | ForEach-Object { $_.hostname } | Where-Object { $_ }
|
||||||
|
Write-Log "Found $($computers.Count) shopfloor PCs" -Level "INFO"
|
||||||
|
} elseif ($ComputerListFile) {
|
||||||
|
if (Test-Path $ComputerListFile) {
|
||||||
|
$computers = Get-Content $ComputerListFile | Where-Object { $_.Trim() -and -not $_.StartsWith("#") }
|
||||||
|
} else {
|
||||||
|
Write-Log "Computer list file not found: $ComputerListFile" -Level "ERROR"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
} elseif ($ComputerName) {
|
||||||
|
$computers = $ComputerName
|
||||||
|
} else {
|
||||||
|
Write-Log "No computers specified. Use -ComputerName, -ComputerListFile, or -All" -Level "ERROR"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($computers.Count -eq 0) {
|
||||||
|
Write-Log "No computers to process." -Level "ERROR"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Target computers: $($computers.Count)" -Level "INFO"
|
||||||
|
Write-Log "Task: $Task" -Level "TASK"
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# Build FQDNs
|
||||||
|
$targetFQDNs = $computers | ForEach-Object {
|
||||||
|
if ($_ -like "*.*") { $_ } else { "$_.$DnsSuffix" }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Determine which tasks to run
|
||||||
|
$tasksToRun = @($Task)
|
||||||
|
|
||||||
|
# Create session options
|
||||||
|
$sessionOption = New-PSSessionOption -OpenTimeout 30000 -OperationTimeout 600000 -NoMachineProfile
|
||||||
|
|
||||||
|
# Process each task
|
||||||
|
foreach ($currentTask in $tasksToRun) {
|
||||||
|
|
||||||
|
if ($tasksToRun.Count -gt 1) {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Log "Running task: $currentTask" -Level "TASK"
|
||||||
|
}
|
||||||
|
|
||||||
|
$scriptBlock = $TaskScripts[$currentTask]
|
||||||
|
|
||||||
|
if (-not $scriptBlock) {
|
||||||
|
Write-Log "Unknown task: $currentTask" -Level "ERROR"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
# Execute on remote computers
|
||||||
|
$sessionParams = @{
|
||||||
|
ComputerName = $targetFQDNs
|
||||||
|
ScriptBlock = $scriptBlock
|
||||||
|
Credential = $Credential
|
||||||
|
SessionOption = $sessionOption
|
||||||
|
ErrorAction = 'SilentlyContinue'
|
||||||
|
ErrorVariable = 'remoteErrors'
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ThrottleLimit -and $PSVersionTable.PSVersion.Major -ge 7) {
|
||||||
|
$sessionParams.ThrottleLimit = $ThrottleLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Executing on $($targetFQDNs.Count) computer(s)..." -Level "INFO"
|
||||||
|
|
||||||
|
$results = Invoke-Command @sessionParams
|
||||||
|
|
||||||
|
# Process results
|
||||||
|
$successCount = 0
|
||||||
|
$failCount = 0
|
||||||
|
|
||||||
|
foreach ($result in $results) {
|
||||||
|
if ($result.Success) {
|
||||||
|
Write-Log "[OK] $($result.Hostname)" -Level "SUCCESS"
|
||||||
|
|
||||||
|
# Display task-specific output
|
||||||
|
switch ($currentTask) {
|
||||||
|
'OptimizeDisk' {
|
||||||
|
foreach ($drive in $result.Drives) {
|
||||||
|
$status = if ($drive.Success) { "OK" } else { "FAIL" }
|
||||||
|
Write-Host " Drive $($drive.DriveLetter): $($drive.MediaType) - $($drive.Action) [$status]" -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'DISM' {
|
||||||
|
Write-Host " Duration: $($result.Duration) minutes, Exit code: $($result.ExitCode)" -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
'SFC' {
|
||||||
|
Write-Host " $($result.Summary), Duration: $($result.Duration) minutes" -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
'DiskCleanup' {
|
||||||
|
Write-Host " Space freed: $($result.SpaceFreed) GB" -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
if ($result.Output) {
|
||||||
|
Write-Host " $($result.Output)" -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$successCount++
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Write-Log "[FAIL] $($result.Hostname): $($result.Error)" -Level "ERROR"
|
||||||
|
$failCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Handle connection errors
|
||||||
|
foreach ($err in $remoteErrors) {
|
||||||
|
$target = if ($err.TargetObject) { $err.TargetObject } else { "Unknown" }
|
||||||
|
Write-Log "[FAIL] ${target}: $($err.Exception.Message)" -Level "ERROR"
|
||||||
|
$failCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "=" * 70 -ForegroundColor Cyan
|
||||||
|
Write-Host " SUMMARY" -ForegroundColor Cyan
|
||||||
|
Write-Host "=" * 70 -ForegroundColor Cyan
|
||||||
|
Write-Host " Task: $Task" -ForegroundColor White
|
||||||
|
Write-Host " Successful: $successCount" -ForegroundColor Green
|
||||||
|
Write-Host " Failed: $failCount" -ForegroundColor $(if ($failCount -gt 0) { "Red" } else { "White" })
|
||||||
|
Write-Host "=" * 70 -ForegroundColor Cyan
|
||||||
@@ -25,6 +25,57 @@ Or run PowerShell directly:
|
|||||||
|
|
||||||
## PowerShell Scripts
|
## PowerShell Scripts
|
||||||
|
|
||||||
|
### Invoke-RemoteMaintenance.ps1
|
||||||
|
**Remote maintenance toolkit** - Execute maintenance tasks on shopfloor PCs via WinRM.
|
||||||
|
|
||||||
|
**Available Tasks:**
|
||||||
|
|
||||||
|
| Category | Task | Description |
|
||||||
|
|----------|------|-------------|
|
||||||
|
| **Repair** | `DISM` | Run DISM /Online /Cleanup-Image /RestoreHealth |
|
||||||
|
| | `SFC` | Run SFC /scannow (System File Checker) |
|
||||||
|
| **Optimization** | `OptimizeDisk` | TRIM for SSD, Defrag for HDD |
|
||||||
|
| | `DiskCleanup` | Windows Disk Cleanup (temp files, updates) |
|
||||||
|
| | `ClearUpdateCache` | Clear Windows Update cache (fixes stuck updates) |
|
||||||
|
| | `ClearBrowserCache` | Clear Chrome/Edge cache files |
|
||||||
|
| **Services** | `RestartSpooler` | Restart Print Spooler service |
|
||||||
|
| | `FlushDNS` | Clear DNS resolver cache |
|
||||||
|
| | `RestartWinRM` | Restart WinRM service |
|
||||||
|
| **Time/Date** | `SetTimezone` | Set timezone to Eastern Standard Time |
|
||||||
|
| | `SyncTime` | Force time sync with domain controller |
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```powershell
|
||||||
|
# Run DISM on a single PC
|
||||||
|
.\Invoke-RemoteMaintenance.ps1 -ComputerName "G1ZTNCX3ESF" -Task DISM
|
||||||
|
|
||||||
|
# Optimize disks on multiple PCs
|
||||||
|
.\Invoke-RemoteMaintenance.ps1 -ComputerName "PC01","PC02" -Task OptimizeDisk
|
||||||
|
|
||||||
|
# Run disk cleanup on all shopfloor PCs
|
||||||
|
.\Invoke-RemoteMaintenance.ps1 -All -Task DiskCleanup
|
||||||
|
|
||||||
|
# Clear Windows Update cache (fixes stuck updates)
|
||||||
|
.\Invoke-RemoteMaintenance.ps1 -ComputerName "PC01" -Task ClearUpdateCache
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
| Parameter | Default | Description |
|
||||||
|
|-----------|---------|-------------|
|
||||||
|
| `-ComputerName` | - | Single or multiple computer names/IPs |
|
||||||
|
| `-ComputerListFile` | - | Path to text file with computer list |
|
||||||
|
| `-All` | - | Target all shopfloor PCs from ShopDB |
|
||||||
|
| `-Task` | (required) | Maintenance task to execute |
|
||||||
|
| `-Credential` | (prompts) | PSCredential for authentication |
|
||||||
|
| `-ThrottleLimit` | `5` | Maximum concurrent sessions |
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- DISM and SFC tasks can take 10-30 minutes per PC
|
||||||
|
- OptimizeDisk automatically detects SSD vs HDD
|
||||||
|
- ClearUpdateCache stops Windows Update service, clears cache, restarts service
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Invoke-RemoteAssetCollection.ps1
|
### Invoke-RemoteAssetCollection.ps1
|
||||||
**Remote collection via WinRM HTTP** - Execute asset collection on multiple PCs using WinRM over HTTP (port 5985).
|
**Remote collection via WinRM HTTP** - Execute asset collection on multiple PCs using WinRM over HTTP (port 5985).
|
||||||
|
|
||||||
@@ -151,28 +202,33 @@ $cred = Get-Credential
|
|||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────┐
|
┌──────────────────────────────────────────────────────────────┐
|
||||||
│ Management Server │
|
│ Management Server │
|
||||||
│ ┌───────────────────────────────┐ │
|
│ ┌────────────────────────────────────────────────────────┐ │
|
||||||
│ │ Invoke-RemoteAssetCollection │ │
|
│ │ Update-ShopfloorPCs-Remote.ps1 - Data collection │ │
|
||||||
│ │ Update-ShopfloorPCs-Remote │ │
|
│ │ Invoke-RemoteMaintenance.ps1 - Maintenance tasks │ │
|
||||||
│ └──────────────┬────────────────┘ │
|
│ │ Invoke-RemoteAssetCollection.ps1 - General execution │ │
|
||||||
└─────────────────┼───────────────────┘
|
│ └────────────────────────┬───────────────────────────────┘ │
|
||||||
│ WinRM (5985/5986)
|
└───────────────────────────┼──────────────────────────────────┘
|
||||||
▼
|
│ WinRM (5985/5986)
|
||||||
┌─────────────────────────────────────┐
|
▼
|
||||||
│ Shopfloor PC 1 │
|
┌──────────────────────────────────────────────────────────────┐
|
||||||
│ ┌───────────────────────────────┐ │
|
│ Shopfloor PCs │
|
||||||
│ │ Update-PC-CompleteAsset.ps1 │ │
|
│ ┌────────────────────────────────────────────────────────┐ │
|
||||||
│ └───────────────────────────────┘ │
|
│ │ Data Collection: │ │
|
||||||
└─────────────────────────────────────┘
|
│ │ - System info, network, DNC config, installed apps │ │
|
||||||
┌─────────────────────────────────────┐
|
│ │ │ │
|
||||||
│ Shopfloor PC 2 │
|
│ │ Maintenance Tasks: │ │
|
||||||
│ ┌───────────────────────────────┐ │
|
│ │ - DISM, SFC, Disk Cleanup, Optimize Disk │ │
|
||||||
│ │ Update-PC-CompleteAsset.ps1 │ │
|
│ │ - Restart Spooler, Flush DNS, Clear Caches │ │
|
||||||
│ └───────────────────────────────┘ │
|
│ └────────────────────────────────────────────────────────┘ │
|
||||||
└─────────────────────────────────────┘
|
└──────────────────────────────────────────────────────────────┘
|
||||||
... (parallel execution)
|
│
|
||||||
|
▼ HTTPS POST
|
||||||
|
┌──────────────────────────────────────────────────────────────┐
|
||||||
|
│ ShopDB API │
|
||||||
|
│ api.asp -> MySQL (machines, communications, dncconfig) │
|
||||||
|
└──────────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
## WinRM Setup
|
## WinRM Setup
|
||||||
|
|||||||
40
tools/Enable-WinRM.bat
Normal file
40
tools/Enable-WinRM.bat
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
@echo off
|
||||||
|
:: Enable WinRM for Remote Management
|
||||||
|
:: Run as Administrator
|
||||||
|
|
||||||
|
echo ============================================
|
||||||
|
echo Enable WinRM for ShopDB Remote Management
|
||||||
|
echo ============================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
:: Check for admin rights
|
||||||
|
net session >nul 2>&1
|
||||||
|
if %errorLevel% neq 0 (
|
||||||
|
echo ERROR: Please run as Administrator!
|
||||||
|
echo Right-click and select "Run as administrator"
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo Enabling WinRM...
|
||||||
|
powershell -Command "Enable-PSRemoting -Force -SkipNetworkProfileCheck" 2>nul
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo Setting firewall rules...
|
||||||
|
powershell -Command "Set-NetFirewallRule -Name 'WINRM-HTTP-In-TCP' -Enabled True -Profile Any" 2>nul
|
||||||
|
netsh advfirewall firewall add rule name="WinRM-HTTP" dir=in localport=5985 protocol=TCP action=allow >nul 2>&1
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo Starting WinRM service...
|
||||||
|
sc config winrm start= auto >nul
|
||||||
|
net start winrm >nul 2>&1
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ============================================
|
||||||
|
echo WinRM Enabled Successfully!
|
||||||
|
echo This PC can now be remotely managed.
|
||||||
|
echo ============================================
|
||||||
|
echo.
|
||||||
|
echo Hostname: %COMPUTERNAME%
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
Reference in New Issue
Block a user