Add fixnetworkshare, winrm-setup-package, udc remote-execution suites
- NetworkDriveManager.ps1: S: drive repair utility - winrm-setup-package: Invoke-RemoteTask helper + Setup-WinRM.bat + HTML guide - remote-execution/udc: UDC_Update.ps1 and batch wrappers for updating DNC controllers on shop-floor PCs - Invoke-RemoteMaintenance.ps1: substantial rework (~1650 lines) - Schedule-Maintenance and complete-asset minor updates - Bump edncfix gitlink to v1.6.0 (2748bfa) - .gitignore: block inventory.csv/xlsx (CUI) and logs_*.txt (per-host logs) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
535
winrm-setup-package/Invoke-RemoteTask.ps1
Normal file
535
winrm-setup-package/Invoke-RemoteTask.ps1
Normal file
@@ -0,0 +1,535 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Simple remote maintenance toolkit for Windows PCs via WinRM.
|
||||
|
||||
.DESCRIPTION
|
||||
Executes maintenance tasks on remote Windows PCs using WinRM.
|
||||
Reads target computers from a text file (one hostname/IP per line).
|
||||
|
||||
.PARAMETER HostsFile
|
||||
Path to text file containing computer names/IPs (one per line).
|
||||
Lines starting with # are treated as comments.
|
||||
Default: .\hosts.txt
|
||||
|
||||
.PARAMETER ComputerName
|
||||
Single computer name or IP address (alternative to HostsFile).
|
||||
|
||||
.PARAMETER Task
|
||||
Maintenance task to execute. Available tasks:
|
||||
- RestartSpooler : Restart Print Spooler service
|
||||
- FlushDNS : Clear DNS resolver cache
|
||||
- ClearTempFiles : Clear Windows temp files
|
||||
- DiskCleanup : Run Windows Disk Cleanup
|
||||
- OptimizeDisk : TRIM (SSD) or Defrag (HDD)
|
||||
- SyncTime : Force time sync with domain controller
|
||||
- RestartService : Restart a specific Windows service
|
||||
- RunCommand : Run a custom command
|
||||
- RestartComputer : Restart the remote PC (requires confirmation)
|
||||
|
||||
.PARAMETER ServiceName
|
||||
Service name for RestartService task.
|
||||
|
||||
.PARAMETER Command
|
||||
Custom command for RunCommand task.
|
||||
|
||||
.PARAMETER Credential
|
||||
PSCredential for remote authentication. Prompts if not provided.
|
||||
|
||||
.PARAMETER DnsSuffix
|
||||
DNS suffix to append to hostnames (if not already FQDN).
|
||||
Default: logon.ds.ge.com
|
||||
|
||||
.PARAMETER ThrottleLimit
|
||||
Maximum number of concurrent remote connections.
|
||||
Default: 10
|
||||
|
||||
.PARAMETER LogResults
|
||||
Save results to a timestamped log file in the script directory.
|
||||
Log files are saved as: RemoteTask_YYYYMMDD_HHMMSS.log
|
||||
|
||||
.EXAMPLE
|
||||
# Restart print spooler on all hosts in hosts.txt
|
||||
.\Invoke-RemoteTask.ps1 -Task RestartSpooler
|
||||
|
||||
.EXAMPLE
|
||||
# Flush DNS on a single computer
|
||||
.\Invoke-RemoteTask.ps1 -ComputerName "PC001" -Task FlushDNS
|
||||
|
||||
.EXAMPLE
|
||||
# Run disk cleanup on hosts from custom file
|
||||
.\Invoke-RemoteTask.ps1 -HostsFile ".\shopfloor-pcs.txt" -Task DiskCleanup
|
||||
|
||||
.EXAMPLE
|
||||
# Restart a specific service
|
||||
.\Invoke-RemoteTask.ps1 -Task RestartService -ServiceName "Spooler"
|
||||
|
||||
.EXAMPLE
|
||||
# Run custom command
|
||||
.\Invoke-RemoteTask.ps1 -Task RunCommand -Command "Get-Process | Select -First 5"
|
||||
|
||||
.NOTES
|
||||
Author: Shop Floor Tools
|
||||
Requirements: PowerShell 5.1+, WinRM enabled on targets
|
||||
#>
|
||||
|
||||
[CmdletBinding(DefaultParameterSetName='ByFile')]
|
||||
param(
|
||||
[Parameter(ParameterSetName='ByFile')]
|
||||
[string]$HostsFile = ".\hosts.txt",
|
||||
|
||||
[Parameter(ParameterSetName='ByName')]
|
||||
[string[]]$ComputerName,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
[ValidateSet(
|
||||
'RestartSpooler', 'FlushDNS', 'ClearTempFiles', 'DiskCleanup',
|
||||
'OptimizeDisk', 'SyncTime', 'RestartService', 'RunCommand',
|
||||
'GetDiskSpace', 'GetUptime', 'TestConnection', 'RestartComputer'
|
||||
)]
|
||||
[string]$Task,
|
||||
|
||||
[Parameter()]
|
||||
[string]$ServiceName,
|
||||
|
||||
[Parameter()]
|
||||
[string]$Command,
|
||||
|
||||
[Parameter()]
|
||||
[PSCredential]$Credential,
|
||||
|
||||
[Parameter()]
|
||||
[string]$DnsSuffix = "logon.ds.ge.com",
|
||||
|
||||
[Parameter()]
|
||||
[int]$ThrottleLimit = 10,
|
||||
|
||||
[Parameter()]
|
||||
[switch]$LogResults
|
||||
)
|
||||
|
||||
# =============================================================================
|
||||
# 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
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Task Scriptblocks
|
||||
# =============================================================================
|
||||
|
||||
$TaskScripts = @{
|
||||
|
||||
'RestartSpooler' = {
|
||||
$result = @{ Success = $false; Hostname = $env:COMPUTERNAME; Output = ""; Error = $null }
|
||||
try {
|
||||
Stop-Service -Name Spooler -Force -ErrorAction Stop
|
||||
$queuePath = "$env:SystemRoot\System32\spool\PRINTERS"
|
||||
if (Test-Path $queuePath) { Remove-Item "$queuePath\*" -Force -ErrorAction SilentlyContinue }
|
||||
Start-Service -Name Spooler -ErrorAction Stop
|
||||
$status = (Get-Service -Name Spooler).Status
|
||||
$result.Success = ($status -eq 'Running')
|
||||
$result.Output = "Print Spooler restarted. Status: $status"
|
||||
} catch { $result.Error = $_.Exception.Message }
|
||||
return $result
|
||||
}
|
||||
|
||||
'FlushDNS' = {
|
||||
$result = @{ Success = $false; Hostname = $env:COMPUTERNAME; Output = ""; Error = $null }
|
||||
try {
|
||||
$flushResult = & ipconfig /flushdns 2>&1
|
||||
$result.Output = ($flushResult -join " ").Trim()
|
||||
$result.Success = $true
|
||||
} catch { $result.Error = $_.Exception.Message }
|
||||
return $result
|
||||
}
|
||||
|
||||
'ClearTempFiles' = {
|
||||
$result = @{ Success = $false; Hostname = $env:COMPUTERNAME; Output = ""; Error = $null; FilesDeleted = 0 }
|
||||
try {
|
||||
$tempPaths = @("$env:TEMP", "$env:SystemRoot\Temp")
|
||||
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; $result.FilesDeleted++ } catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
$result.Success = $true
|
||||
$result.Output = "Deleted $($result.FilesDeleted) temp files"
|
||||
} catch { $result.Error = $_.Exception.Message }
|
||||
return $result
|
||||
}
|
||||
|
||||
'DiskCleanup' = {
|
||||
$result = @{ Success = $false; Hostname = $env:COMPUTERNAME; Output = ""; Error = $null; SpaceFreedMB = 0 }
|
||||
try {
|
||||
$initialFree = (Get-PSDrive C).Free
|
||||
$cleanupPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches"
|
||||
$categories = @("Temporary Files", "Temporary Setup Files", "Old ChkDsk Files", "Windows Update Cleanup", "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 }
|
||||
}
|
||||
Start-Process -FilePath "cleanmgr.exe" -ArgumentList "/sagerun:100" -Wait -WindowStyle Hidden
|
||||
Start-Sleep -Seconds 2
|
||||
$finalFree = (Get-PSDrive C).Free
|
||||
$result.SpaceFreedMB = [math]::Round(($finalFree - $initialFree) / 1MB, 0)
|
||||
$result.Success = $true
|
||||
$result.Output = "Disk cleanup completed. Space freed: $($result.SpaceFreedMB) MB"
|
||||
} catch { $result.Error = $_.Exception.Message }
|
||||
return $result
|
||||
}
|
||||
|
||||
'OptimizeDisk' = {
|
||||
$result = @{ Success = $false; Hostname = $env:COMPUTERNAME; Output = ""; Error = $null }
|
||||
try {
|
||||
$volumes = Get-Volume | Where-Object { $_.DriveType -eq 'Fixed' -and $_.DriveLetter }
|
||||
$optimized = @()
|
||||
foreach ($vol in $volumes) {
|
||||
$driveLetter = $vol.DriveLetter
|
||||
$physicalDisk = Get-PhysicalDisk | Where-Object { $_.DeviceId -eq (Get-Partition -DriveLetter $driveLetter -ErrorAction SilentlyContinue).DiskNumber }
|
||||
$mediaType = if ($physicalDisk) { $physicalDisk.MediaType } else { "Unknown" }
|
||||
try {
|
||||
if ($mediaType -eq 'SSD') { Optimize-Volume -DriveLetter $driveLetter -ReTrim -ErrorAction Stop; $action = "TRIM" }
|
||||
else { Optimize-Volume -DriveLetter $driveLetter -Defrag -ErrorAction Stop; $action = "Defrag" }
|
||||
$optimized += "${driveLetter}:($action)"
|
||||
} catch { $optimized += "${driveLetter}:(Failed)" }
|
||||
}
|
||||
$result.Success = $true
|
||||
$result.Output = "Optimized: $($optimized -join ', ')"
|
||||
} catch { $result.Error = $_.Exception.Message }
|
||||
return $result
|
||||
}
|
||||
|
||||
'SyncTime' = {
|
||||
$result = @{ Success = $false; Hostname = $env:COMPUTERNAME; Output = ""; Error = $null }
|
||||
try {
|
||||
$source = (& w32tm /query /source 2>&1) -join " "
|
||||
$syncResult = & w32tm /resync /force 2>&1
|
||||
$result.Success = ($syncResult -match "success" -or $LASTEXITCODE -eq 0)
|
||||
$result.Output = "Time synced with $source. Current: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
|
||||
} catch { $result.Error = $_.Exception.Message }
|
||||
return $result
|
||||
}
|
||||
|
||||
'RestartService' = {
|
||||
param($ServiceName)
|
||||
$result = @{ Success = $false; Hostname = $env:COMPUTERNAME; Output = ""; Error = $null }
|
||||
if (-not $ServiceName) { $result.Error = "ServiceName parameter required"; return $result }
|
||||
try {
|
||||
Restart-Service -Name $ServiceName -Force -ErrorAction Stop
|
||||
$status = (Get-Service -Name $ServiceName).Status
|
||||
$result.Success = ($status -eq 'Running')
|
||||
$result.Output = "Service '$ServiceName' restarted. Status: $status"
|
||||
} catch { $result.Error = $_.Exception.Message }
|
||||
return $result
|
||||
}
|
||||
|
||||
'RunCommand' = {
|
||||
param($Command)
|
||||
$result = @{ Success = $false; Hostname = $env:COMPUTERNAME; Output = ""; Error = $null }
|
||||
if (-not $Command) { $result.Error = "Command parameter required"; return $result }
|
||||
try {
|
||||
$output = Invoke-Expression $Command 2>&1
|
||||
$result.Output = ($output | Out-String).Trim()
|
||||
$result.Success = $true
|
||||
} catch { $result.Error = $_.Exception.Message }
|
||||
return $result
|
||||
}
|
||||
|
||||
'GetDiskSpace' = {
|
||||
$result = @{ Success = $false; Hostname = $env:COMPUTERNAME; Output = ""; Error = $null }
|
||||
try {
|
||||
$drives = Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Used -ne $null }
|
||||
$info = foreach ($drive in $drives) {
|
||||
$freeGB = [math]::Round($drive.Free / 1GB, 1)
|
||||
$usedGB = [math]::Round($drive.Used / 1GB, 1)
|
||||
$totalGB = $freeGB + $usedGB
|
||||
$pctFree = if ($totalGB -gt 0) { [math]::Round(($freeGB / $totalGB) * 100, 0) } else { 0 }
|
||||
"$($drive.Name): $freeGB GB free ($pctFree%)"
|
||||
}
|
||||
$result.Output = $info -join ", "
|
||||
$result.Success = $true
|
||||
} catch { $result.Error = $_.Exception.Message }
|
||||
return $result
|
||||
}
|
||||
|
||||
'GetUptime' = {
|
||||
$result = @{ Success = $false; Hostname = $env:COMPUTERNAME; Output = ""; Error = $null }
|
||||
try {
|
||||
$os = Get-CimInstance -ClassName Win32_OperatingSystem
|
||||
$uptime = (Get-Date) - $os.LastBootUpTime
|
||||
$result.Output = "Up $([math]::Floor($uptime.TotalDays))d $($uptime.Hours)h $($uptime.Minutes)m (Last boot: $($os.LastBootUpTime.ToString('yyyy-MM-dd HH:mm')))"
|
||||
$result.Success = $true
|
||||
} catch { $result.Error = $_.Exception.Message }
|
||||
return $result
|
||||
}
|
||||
|
||||
'TestConnection' = {
|
||||
$result = @{ Success = $true; Hostname = $env:COMPUTERNAME; Output = "Connection successful"; Error = $null }
|
||||
return $result
|
||||
}
|
||||
|
||||
'RestartComputer' = {
|
||||
$result = @{ Success = $false; Hostname = $env:COMPUTERNAME; Output = ""; Error = $null }
|
||||
try {
|
||||
$result.Output = "Restart initiated"
|
||||
$result.Success = $true
|
||||
# Schedule restart in 5 seconds to allow response to return
|
||||
Start-Process -FilePath "shutdown.exe" -ArgumentList "/r /t 5 /c `"Remote restart initiated via WinRM`"" -NoNewWindow
|
||||
} catch { $result.Error = $_.Exception.Message }
|
||||
return $result
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Main Execution
|
||||
# =============================================================================
|
||||
|
||||
Write-Host ""
|
||||
Write-Host ("=" * 60) -ForegroundColor Cyan
|
||||
Write-Host " Remote Task Executor - Task: $Task" -ForegroundColor Cyan
|
||||
Write-Host ("=" * 60) -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Validate task-specific parameters
|
||||
if ($Task -eq 'RestartService' -and -not $ServiceName) {
|
||||
Write-Log "ServiceName parameter is required for RestartService task" -Level "ERROR"
|
||||
exit 1
|
||||
}
|
||||
if ($Task -eq 'RunCommand' -and -not $Command) {
|
||||
Write-Log "Command parameter is required for RunCommand task" -Level "ERROR"
|
||||
exit 1
|
||||
}
|
||||
if ($Task -eq 'RestartComputer') {
|
||||
Write-Host ""
|
||||
Write-Host "WARNING: This will restart the target computer(s)!" -ForegroundColor Yellow
|
||||
$confirm = Read-Host "Type 'YES' to confirm"
|
||||
if ($confirm -ne 'YES') {
|
||||
Write-Log "Restart cancelled by user" -Level "WARNING"
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
|
||||
# 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 ($ComputerName) {
|
||||
$computers = $ComputerName
|
||||
} else {
|
||||
if (-not (Test-Path $HostsFile)) {
|
||||
Write-Log "Hosts file not found: $HostsFile" -Level "ERROR"
|
||||
Write-Log "Create a text file with one hostname or IP per line." -Level "INFO"
|
||||
exit 1
|
||||
}
|
||||
$computers = Get-Content $HostsFile | Where-Object { $_.Trim() -and -not $_.StartsWith("#") }
|
||||
}
|
||||
|
||||
if ($computers.Count -eq 0) {
|
||||
Write-Log "No computers specified." -Level "ERROR"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Log "Target computers: $($computers.Count)" -Level "INFO"
|
||||
Write-Host ""
|
||||
|
||||
# Build FQDNs if DNS suffix provided
|
||||
$targets = $computers | ForEach-Object {
|
||||
$name = $_.Trim()
|
||||
if ($DnsSuffix -and $name -notlike "*.*") {
|
||||
"$name.$DnsSuffix"
|
||||
} else {
|
||||
$name
|
||||
}
|
||||
}
|
||||
|
||||
# Get the scriptblock
|
||||
$scriptBlock = $TaskScripts[$Task]
|
||||
|
||||
# Create session options
|
||||
$sessionOption = New-PSSessionOption -OpenTimeout 30000 -OperationTimeout 300000 -NoMachineProfile
|
||||
|
||||
Write-Log "Executing on $($targets.Count) computer(s) in parallel (ThrottleLimit: $ThrottleLimit)..." -Level "INFO"
|
||||
Write-Host ""
|
||||
|
||||
# Build arguments for tasks that need them
|
||||
$taskArgs = @()
|
||||
if ($Task -eq 'RestartService') { $taskArgs = @($ServiceName) }
|
||||
if ($Task -eq 'RunCommand') { $taskArgs = @($Command) }
|
||||
|
||||
# Show progress indicator
|
||||
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
|
||||
Write-Host " [" -NoNewline
|
||||
Write-Host "Running..." -ForegroundColor Yellow -NoNewline
|
||||
Write-Host "] Please wait..." -NoNewline
|
||||
|
||||
# Execute on all remote computers in parallel using Invoke-Command
|
||||
$results = @()
|
||||
$connectionErrors = @()
|
||||
|
||||
try {
|
||||
if ($taskArgs.Count -gt 0) {
|
||||
$results = Invoke-Command -ComputerName $targets -ScriptBlock $scriptBlock -ArgumentList $taskArgs `
|
||||
-Credential $Credential -SessionOption $sessionOption -Authentication Negotiate `
|
||||
-ThrottleLimit $ThrottleLimit -ErrorAction SilentlyContinue -ErrorVariable connectionErrors
|
||||
} else {
|
||||
$results = Invoke-Command -ComputerName $targets -ScriptBlock $scriptBlock `
|
||||
-Credential $Credential -SessionOption $sessionOption -Authentication Negotiate `
|
||||
-ThrottleLimit $ThrottleLimit -ErrorAction SilentlyContinue -ErrorVariable connectionErrors
|
||||
}
|
||||
} catch {
|
||||
Write-Host ""
|
||||
Write-Log "Execution error: $($_.Exception.Message)" -Level "ERROR"
|
||||
}
|
||||
|
||||
$stopwatch.Stop()
|
||||
$elapsed = $stopwatch.Elapsed.TotalSeconds
|
||||
|
||||
# Clear the progress line
|
||||
Write-Host "`r" -NoNewline
|
||||
Write-Host (" " * 60) -NoNewline
|
||||
Write-Host "`r" -NoNewline
|
||||
Write-Log "Completed in $([math]::Round($elapsed, 1)) seconds" -Level "INFO"
|
||||
Write-Host ""
|
||||
|
||||
# Collect all results (successes, failures, connection errors)
|
||||
$allResults = @()
|
||||
$successCount = 0
|
||||
$failCount = 0
|
||||
|
||||
foreach ($result in $results) {
|
||||
$status = if ($result.Success) { "OK"; $successCount++ } else { "FAIL"; $failCount++ }
|
||||
$message = if ($result.Success) { $result.Output } else { $result.Error }
|
||||
$allResults += [PSCustomObject]@{
|
||||
Status = $status
|
||||
Computer = $result.Hostname
|
||||
Message = $message
|
||||
}
|
||||
}
|
||||
|
||||
# Process connection errors
|
||||
foreach ($err in $connectionErrors) {
|
||||
$targetName = if ($err.TargetObject) { $err.TargetObject } else { "Unknown" }
|
||||
# Extract just the computer name from FQDN for display
|
||||
$shortName = ($targetName -split '\.')[0]
|
||||
$errorMsg = $err.Exception.Message -replace '\r?\n', ' '
|
||||
# Truncate long error messages
|
||||
if ($errorMsg.Length -gt 60) { $errorMsg = $errorMsg.Substring(0, 57) + "..." }
|
||||
$allResults += [PSCustomObject]@{
|
||||
Status = "FAIL"
|
||||
Computer = $shortName
|
||||
Message = $errorMsg
|
||||
}
|
||||
$failCount++
|
||||
}
|
||||
|
||||
# Sort results: failures first, then successes
|
||||
$allResults = $allResults | Sort-Object @{Expression={$_.Status}; Descending=$true}, Computer
|
||||
|
||||
# Display results in a formatted table
|
||||
Write-Host " STATUS COMPUTER RESULT" -ForegroundColor Cyan
|
||||
Write-Host " ------ -------- ------" -ForegroundColor Cyan
|
||||
|
||||
foreach ($r in $allResults) {
|
||||
$statusColor = if ($r.Status -eq "OK") { "Green" } else { "Red" }
|
||||
$statusIcon = if ($r.Status -eq "OK") { "[OK] " } else { "[FAIL]" }
|
||||
|
||||
# Pad/truncate computer name to 20 chars
|
||||
$compName = $r.Computer
|
||||
if ($compName.Length -gt 18) { $compName = $compName.Substring(0, 15) + "..." }
|
||||
$compName = $compName.PadRight(20)
|
||||
|
||||
# Truncate message if too long
|
||||
$msg = $r.Message
|
||||
if ($msg.Length -gt 50) { $msg = $msg.Substring(0, 47) + "..." }
|
||||
|
||||
Write-Host " " -NoNewline
|
||||
Write-Host $statusIcon -ForegroundColor $statusColor -NoNewline
|
||||
Write-Host " $compName " -NoNewline
|
||||
Write-Host $msg -ForegroundColor $(if ($r.Status -eq "OK") { "White" } else { "Yellow" })
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Summary
|
||||
Write-Host ""
|
||||
Write-Host ("=" * 60) -ForegroundColor Cyan
|
||||
Write-Host " SUMMARY" -ForegroundColor Cyan
|
||||
Write-Host ("=" * 60) -ForegroundColor Cyan
|
||||
Write-Host " Task: $Task" -ForegroundColor White
|
||||
Write-Host " Total: $($computers.Count)" -ForegroundColor White
|
||||
Write-Host " Successful: $successCount" -ForegroundColor Green
|
||||
Write-Host " Failed: $failCount" -ForegroundColor $(if ($failCount -gt 0) { "Red" } else { "White" })
|
||||
Write-Host ("=" * 60) -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Save to log file if requested
|
||||
if ($LogResults) {
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
if (-not $scriptDir) { $scriptDir = Get-Location }
|
||||
$logTimestamp = Get-Date -Format "yyyyMMdd_HHmmss"
|
||||
$logFile = Join-Path $scriptDir "RemoteTask_$logTimestamp.log"
|
||||
|
||||
$logContent = @()
|
||||
$logContent += "=" * 60
|
||||
$logContent += "Remote Task Execution Log"
|
||||
$logContent += "=" * 60
|
||||
$logContent += "Date/Time: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
|
||||
$logContent += "Task: $Task"
|
||||
$logContent += "Targets: $($computers.Count)"
|
||||
$logContent += "ThrottleLimit: $ThrottleLimit"
|
||||
$logContent += "Elapsed: $([math]::Round($elapsed, 1)) seconds"
|
||||
if ($ServiceName) { $logContent += "ServiceName: $ServiceName" }
|
||||
if ($Command) { $logContent += "Command: $Command" }
|
||||
$logContent += ""
|
||||
$logContent += "=" * 60
|
||||
$logContent += "RESULTS"
|
||||
$logContent += "=" * 60
|
||||
$logContent += ""
|
||||
$logContent += "STATUS COMPUTER RESULT"
|
||||
$logContent += "------ -------- ------"
|
||||
|
||||
foreach ($r in $allResults) {
|
||||
$statusText = if ($r.Status -eq "OK") { "[OK] " } else { "[FAIL] " }
|
||||
$compText = $r.Computer.PadRight(28)
|
||||
$logContent += "$statusText $compText $($r.Message)"
|
||||
}
|
||||
|
||||
$logContent += ""
|
||||
$logContent += "=" * 60
|
||||
$logContent += "SUMMARY"
|
||||
$logContent += "=" * 60
|
||||
$logContent += "Total: $($computers.Count)"
|
||||
$logContent += "Successful: $successCount"
|
||||
$logContent += "Failed: $failCount"
|
||||
$logContent += "=" * 60
|
||||
|
||||
$logContent | Out-File -FilePath $logFile -Encoding UTF8
|
||||
Write-Log "Results saved to: $logFile" -Level "SUCCESS"
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# Results are displayed above and optionally saved to log file
|
||||
# To capture results programmatically, use: $results = Invoke-Command ... directly
|
||||
296
winrm-setup-package/README.md
Normal file
296
winrm-setup-package/README.md
Normal file
@@ -0,0 +1,296 @@
|
||||
# WinRM Setup Package for Shopfloor PCs
|
||||
|
||||
This package provides scripts to configure WinRM (Windows Remote Management) on shopfloor PCs and execute remote maintenance tasks.
|
||||
|
||||
## Contents
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `Setup-WinRM.bat` | Run on each PC to enable and configure WinRM |
|
||||
| `Invoke-RemoteTask.ps1` | PowerShell script to execute tasks on remote PCs |
|
||||
| `hosts.txt` | List of target computers (edit before use) |
|
||||
| `README.md` | This documentation |
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Step 1: Configure Your Admin Workstation
|
||||
|
||||
Before connecting to remote PCs, run this once on your admin workstation (as Administrator):
|
||||
|
||||
```powershell
|
||||
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "*.logon.ds.ge.com" -Force
|
||||
```
|
||||
|
||||
This allows your workstation to connect to any PC in the domain.
|
||||
|
||||
### Step 2: Configure the Setup Script
|
||||
|
||||
Edit `Setup-WinRM.bat` and update these values at the top:
|
||||
|
||||
```batch
|
||||
REM Default security group - who can use WinRM to connect
|
||||
set "DEFAULT_SECURITY_GROUP=logon\groupid"
|
||||
|
||||
REM Where to log the inventory CSV (network share recommended)
|
||||
set "DEFAULT_LOG_PATH=\\server\share\winrm-inventory"
|
||||
|
||||
REM Domain suffix for TrustedHosts
|
||||
set "TRUSTED_DOMAIN=*.logon.ds.ge.com"
|
||||
|
||||
REM Optional: Trust a specific subnet (uncomment and set)
|
||||
REM set "TRUSTED_SUBNET=10.48.130.*"
|
||||
```
|
||||
|
||||
### Step 3: Create Security Group in Active Directory
|
||||
|
||||
1. Open **Active Directory Users and Computers**
|
||||
2. Create a new Security Group (or use existing group matching `groupid`)
|
||||
3. Add users who should have remote management access
|
||||
|
||||
### Step 4: Run Setup on Each Shopfloor PC
|
||||
|
||||
Run as Administrator on each PC:
|
||||
|
||||
```cmd
|
||||
Setup-WinRM.bat
|
||||
```
|
||||
|
||||
Or with parameters:
|
||||
```cmd
|
||||
Setup-WinRM.bat "logon\groupid" "\\server\share\winrm-inventory"
|
||||
```
|
||||
|
||||
The script will:
|
||||
- Enable WinRM service
|
||||
- Configure authentication (Negotiate/Kerberos)
|
||||
- Set firewall rules (domain profile only)
|
||||
- Restrict access to the security group
|
||||
- Log hostname/IP to CSV inventory
|
||||
|
||||
### Step 5: Run Remote Tasks
|
||||
|
||||
From your admin workstation, edit `hosts.txt` with target PCs, then:
|
||||
|
||||
```powershell
|
||||
# Test connectivity
|
||||
.\Invoke-RemoteTask.ps1 -Task TestConnection
|
||||
|
||||
# Restart print spooler on all hosts
|
||||
.\Invoke-RemoteTask.ps1 -Task RestartSpooler
|
||||
|
||||
# Check disk space
|
||||
.\Invoke-RemoteTask.ps1 -Task GetDiskSpace
|
||||
|
||||
# Run on a single PC
|
||||
.\Invoke-RemoteTask.ps1 -ComputerName "PC001" -Task FlushDNS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Setup Script Details
|
||||
|
||||
### What Setup-WinRM.bat Configures
|
||||
|
||||
| Setting | Value | Purpose |
|
||||
|---------|-------|---------|
|
||||
| WinRM Service | Auto-start | Ensures WinRM starts on boot |
|
||||
| AllowUnencrypted | false | Security: require encrypted connections |
|
||||
| Negotiate Auth | true | Enables Kerberos/NTLM authentication |
|
||||
| CredSSP Auth | true | Enables credential delegation (double-hop) |
|
||||
| Firewall | Domain profile | Opens port 5985 for domain connections only |
|
||||
| TrustedHosts | *.logon.ds.ge.com | Trusts domain-joined PCs |
|
||||
| RootSDDL | Security group | Restricts who can connect |
|
||||
|
||||
### CSV Inventory
|
||||
|
||||
The setup script logs each PC to a CSV file:
|
||||
|
||||
```csv
|
||||
Hostname,IPAddress,SetupDate,OSVersion,SecurityGroup
|
||||
PC001,10.48.130.101,2026-01-08 09:30:00,10.0,logon\groupid
|
||||
PC002,10.48.130.102,2026-01-08 09:35:00,10.0,logon\groupid
|
||||
```
|
||||
|
||||
You can use this CSV as your hosts file:
|
||||
```powershell
|
||||
# Extract hostnames from CSV
|
||||
Import-Csv "\\server\share\winrm-inventory\winrm-inventory.csv" |
|
||||
Select-Object -ExpandProperty Hostname |
|
||||
Set-Content .\hosts.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Available Remote Tasks
|
||||
|
||||
| Task | Description |
|
||||
|------|-------------|
|
||||
| `TestConnection` | Verify WinRM connectivity |
|
||||
| `GetUptime` | Show system uptime and last boot time |
|
||||
| `GetDiskSpace` | Show free space on all drives |
|
||||
| `RestartSpooler` | Restart Print Spooler service |
|
||||
| `FlushDNS` | Clear DNS resolver cache |
|
||||
| `ClearTempFiles` | Delete Windows temp files |
|
||||
| `DiskCleanup` | Run Windows Disk Cleanup |
|
||||
| `OptimizeDisk` | TRIM (SSD) or Defrag (HDD) |
|
||||
| `SyncTime` | Force time sync with domain controller |
|
||||
| `RestartService` | Restart any Windows service (requires `-ServiceName`) |
|
||||
| `RunCommand` | Run custom PowerShell command (requires `-Command`) |
|
||||
| `RestartComputer` | Restart the remote PC (requires YES confirmation) |
|
||||
|
||||
### Examples
|
||||
|
||||
```powershell
|
||||
# Check uptime on all hosts
|
||||
.\Invoke-RemoteTask.ps1 -Task GetUptime
|
||||
|
||||
# Restart a specific service
|
||||
.\Invoke-RemoteTask.ps1 -Task RestartService -ServiceName "Spooler"
|
||||
|
||||
# Run custom command
|
||||
.\Invoke-RemoteTask.ps1 -Task RunCommand -Command "Get-Process | Sort CPU -Desc | Select -First 5"
|
||||
|
||||
# Use custom hosts file
|
||||
.\Invoke-RemoteTask.ps1 -HostsFile ".\cnc-machines.txt" -Task FlushDNS
|
||||
|
||||
# Specify DNS suffix for short hostnames
|
||||
.\Invoke-RemoteTask.ps1 -DnsSuffix "logon.ds.ge.com" -Task TestConnection
|
||||
|
||||
# Restart a remote PC (will prompt for confirmation)
|
||||
.\Invoke-RemoteTask.ps1 -ComputerName "PC001" -Task RestartComputer
|
||||
|
||||
# Increase parallelism for faster execution on many PCs
|
||||
.\Invoke-RemoteTask.ps1 -Task FlushDNS -ThrottleLimit 20
|
||||
|
||||
# Save results to a log file
|
||||
.\Invoke-RemoteTask.ps1 -Task GetDiskSpace -LogResults
|
||||
```
|
||||
|
||||
### Logging Results
|
||||
|
||||
Use `-LogResults` to save task output to a timestamped log file in the script directory:
|
||||
|
||||
```powershell
|
||||
.\Invoke-RemoteTask.ps1 -Task RestartSpooler -LogResults
|
||||
# Creates: RemoteTask_20260108_143022.log
|
||||
```
|
||||
|
||||
Log files contain:
|
||||
- Task name and parameters
|
||||
- Execution time
|
||||
- Status of each computer (OK/FAIL)
|
||||
- Result messages
|
||||
- Summary totals
|
||||
|
||||
### Targeting Multiple PCs
|
||||
|
||||
```powershell
|
||||
# Comma-separated list
|
||||
.\Invoke-RemoteTask.ps1 -ComputerName "PC001","PC002","PC003" -Task GetUptime
|
||||
|
||||
# Array variable
|
||||
$pcs = @("PC001", "PC002", "PC003")
|
||||
.\Invoke-RemoteTask.ps1 -ComputerName $pcs -Task FlushDNS
|
||||
|
||||
# From hosts.txt file (default)
|
||||
.\Invoke-RemoteTask.ps1 -Task RestartSpooler
|
||||
|
||||
# From CSV inventory
|
||||
$pcs = (Import-Csv "\\server\share\winrm-inventory.csv").Hostname
|
||||
.\Invoke-RemoteTask.ps1 -ComputerName $pcs -Task GetDiskSpace
|
||||
|
||||
# From Active Directory query
|
||||
$pcs = (Get-ADComputer -Filter "Name -like 'SHOPFLOOR-*'").Name
|
||||
.\Invoke-RemoteTask.ps1 -ComputerName $pcs -Task SyncTime
|
||||
```
|
||||
|
||||
All commands run in parallel (default: 10 concurrent connections, adjust with `-ThrottleLimit`).
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Access Denied" when connecting
|
||||
|
||||
1. Verify you're a member of the WinRM security group
|
||||
2. Check that your credentials are correct
|
||||
3. Verify the target PC ran Setup-WinRM.bat successfully
|
||||
|
||||
### "WinRM cannot complete the operation"
|
||||
|
||||
1. Verify the target PC is reachable: `ping PC001`
|
||||
2. Check WinRM is running on target: `sc query winrm` (on target PC)
|
||||
3. Verify firewall allows port 5985
|
||||
|
||||
### "The WinRM client cannot process the request"
|
||||
|
||||
1. Add target to TrustedHosts on your admin workstation:
|
||||
```powershell
|
||||
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "*.logon.ds.ge.com" -Force
|
||||
```
|
||||
|
||||
### Test WinRM Configuration
|
||||
|
||||
On your admin workstation:
|
||||
```powershell
|
||||
# Test basic connectivity
|
||||
Test-WSMan -ComputerName PC001
|
||||
|
||||
# Test with credentials
|
||||
$cred = Get-Credential
|
||||
Test-WSMan -ComputerName PC001 -Credential $cred -Authentication Negotiate
|
||||
|
||||
# Enter interactive session
|
||||
Enter-PSSession -ComputerName PC001 -Credential $cred -Authentication Negotiate
|
||||
```
|
||||
|
||||
On the target PC:
|
||||
```cmd
|
||||
winrm enumerate winrm/config/listener
|
||||
winrm get winrm/config/service
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **Use Security Groups**: Always restrict WinRM access to a specific AD group
|
||||
2. **Domain Profile Only**: Firewall rules only allow connections on domain networks
|
||||
3. **No Unencrypted Traffic**: AllowUnencrypted is set to false
|
||||
4. **Audit Access**: Enable Windows Security auditing for logon events
|
||||
5. **Credential Protection**: Use dedicated admin accounts, not personal accounts
|
||||
|
||||
---
|
||||
|
||||
## Adding Custom Tasks
|
||||
|
||||
Edit `Invoke-RemoteTask.ps1` and add to the `$TaskScripts` hashtable:
|
||||
|
||||
```powershell
|
||||
'MyCustomTask' = {
|
||||
$result = @{
|
||||
Success = $false
|
||||
Hostname = $env:COMPUTERNAME
|
||||
Output = ""
|
||||
Error = $null
|
||||
}
|
||||
try {
|
||||
# Your code here
|
||||
$result.Output = "Task completed"
|
||||
$result.Success = $true
|
||||
} catch {
|
||||
$result.Error = $_.Exception.Message
|
||||
}
|
||||
return $result
|
||||
}
|
||||
```
|
||||
|
||||
Then add the task name to the `ValidateSet` in the param block.
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions, contact your IT support team.
|
||||
269
winrm-setup-package/Setup-WinRM.bat
Normal file
269
winrm-setup-package/Setup-WinRM.bat
Normal file
@@ -0,0 +1,269 @@
|
||||
@echo off
|
||||
REM ============================================================================
|
||||
REM WinRM Setup Script for Shopfloor PCs
|
||||
REM ============================================================================
|
||||
REM
|
||||
REM PURPOSE: Configures WinRM on a Windows PC and restricts access to members
|
||||
REM of a specific Active Directory security group. Logs setup to CSV.
|
||||
REM
|
||||
REM USAGE: Run as Administrator on each shopfloor PC
|
||||
REM Setup-WinRM.bat [SecurityGroupName] [LogPath]
|
||||
REM
|
||||
REM EXAMPLE: Setup-WinRM.bat "logon\groupid" "\\server\share\winrm-inventory"
|
||||
REM
|
||||
REM REQUIREMENTS:
|
||||
REM - Must be run as Administrator
|
||||
REM - PC must be domain-joined
|
||||
REM - Security group must exist in Active Directory
|
||||
REM
|
||||
REM ============================================================================
|
||||
|
||||
setlocal EnableDelayedExpansion
|
||||
|
||||
REM Check for admin privileges
|
||||
net session >nul 2>&1
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo.
|
||||
echo ERROR: This script must be run as Administrator.
|
||||
echo Right-click and select "Run as administrator"
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM ============================================================================
|
||||
REM Configuration - EDIT THESE VALUES FOR YOUR ENVIRONMENT
|
||||
REM ============================================================================
|
||||
|
||||
REM Default security group (can be overridden by parameter)
|
||||
set "DEFAULT_SECURITY_GROUP=logon\groupid"
|
||||
|
||||
REM Default log path for CSV inventory (can be overridden by parameter)
|
||||
REM Use a network share that all PCs can write to
|
||||
set "DEFAULT_LOG_PATH=\\server\share\winrm-inventory"
|
||||
|
||||
REM Domain suffix for TrustedHosts (e.g., *.logon.ds.ge.com)
|
||||
set "TRUSTED_DOMAIN=*.logon.ds.ge.com"
|
||||
|
||||
REM Optional: Trusted subnets - comma-separated (leave empty to skip)
|
||||
REM For /24 subnet: "10.48.130.*"
|
||||
REM For /23 subnet: "10.48.130.*,10.48.131.*"
|
||||
REM For /22 subnet: "10.48.128.*,10.48.129.*,10.48.130.*,10.48.131.*"
|
||||
REM set "TRUSTED_SUBNET=10.48.130.*,10.48.131.*"
|
||||
set "TRUSTED_SUBNET="
|
||||
|
||||
REM ============================================================================
|
||||
|
||||
REM Get parameters or use defaults
|
||||
set "SECURITY_GROUP=%~1"
|
||||
set "LOG_PATH=%~2"
|
||||
|
||||
if "%SECURITY_GROUP%"=="" set "SECURITY_GROUP=%DEFAULT_SECURITY_GROUP%"
|
||||
if "%LOG_PATH%"=="" set "LOG_PATH=%DEFAULT_LOG_PATH%"
|
||||
|
||||
echo.
|
||||
echo ============================================================================
|
||||
echo WinRM Setup Script
|
||||
echo ============================================================================
|
||||
echo.
|
||||
echo Computer: %COMPUTERNAME%
|
||||
echo Security Group: %SECURITY_GROUP%
|
||||
echo Log Path: %LOG_PATH%
|
||||
echo Trusted Domain: %TRUSTED_DOMAIN%
|
||||
if not "%TRUSTED_SUBNET%"=="" echo Trusted Subnet: %TRUSTED_SUBNET%
|
||||
echo.
|
||||
echo ============================================================================
|
||||
echo.
|
||||
|
||||
REM Step 1: Enable WinRM service
|
||||
echo [1/7] Enabling WinRM service...
|
||||
sc config WinRM start= auto >nul 2>&1
|
||||
net start WinRM >nul 2>&1
|
||||
if %ERRORLEVEL% equ 0 (
|
||||
echo WinRM service started
|
||||
) else (
|
||||
echo WinRM service already running
|
||||
)
|
||||
|
||||
REM Step 2: Run quick config (creates listener, firewall rules)
|
||||
echo [2/7] Running WinRM quick configuration...
|
||||
winrm quickconfig -quiet >nul 2>&1
|
||||
echo Quick config completed
|
||||
|
||||
REM Step 3: Configure WinRM settings
|
||||
echo [3/7] Configuring WinRM settings...
|
||||
|
||||
REM Disable unencrypted traffic (security best practice)
|
||||
winrm set winrm/config/service @{AllowUnencrypted="false"} >nul 2>&1
|
||||
|
||||
REM Enable Negotiate authentication (Kerberos/NTLM)
|
||||
winrm set winrm/config/service/auth @{Negotiate="true"} >nul 2>&1
|
||||
|
||||
REM Enable CredSSP for double-hop scenarios (optional)
|
||||
winrm set winrm/config/service/auth @{CredSSP="true"} >nul 2>&1
|
||||
|
||||
REM Set max concurrent operations
|
||||
winrm set winrm/config/service @{MaxConcurrentOperationsPerUser="50"} >nul 2>&1
|
||||
|
||||
REM Set max memory per shell (512MB)
|
||||
winrm set winrm/config/winrs @{MaxMemoryPerShellMB="512"} >nul 2>&1
|
||||
|
||||
echo WinRM settings configured
|
||||
|
||||
REM Step 4: Configure TrustedHosts on CLIENT side (for the admin workstation)
|
||||
REM This step configures this PC to trust connections TO other PCs
|
||||
echo [4/7] Configuring TrustedHosts...
|
||||
|
||||
REM Build TrustedHosts value
|
||||
set "TRUSTED_HOSTS=%TRUSTED_DOMAIN%"
|
||||
if not "%TRUSTED_SUBNET%"=="" (
|
||||
set "TRUSTED_HOSTS=%TRUSTED_HOSTS%,%TRUSTED_SUBNET%"
|
||||
)
|
||||
|
||||
REM Get current TrustedHosts and append if needed
|
||||
powershell -ExecutionPolicy Bypass -Command ^
|
||||
"$currentTrusted = (Get-Item WSMan:\localhost\Client\TrustedHosts -ErrorAction SilentlyContinue).Value; " ^
|
||||
"$newHosts = '%TRUSTED_HOSTS%'; " ^
|
||||
"if ([string]::IsNullOrEmpty($currentTrusted)) { " ^
|
||||
" Set-Item WSMan:\localhost\Client\TrustedHosts -Value $newHosts -Force; " ^
|
||||
" Write-Host ' Set TrustedHosts: ' $newHosts; " ^
|
||||
"} elseif ($currentTrusted -notlike '*%TRUSTED_DOMAIN%*') { " ^
|
||||
" $combined = $currentTrusted + ',' + $newHosts; " ^
|
||||
" Set-Item WSMan:\localhost\Client\TrustedHosts -Value $combined -Force; " ^
|
||||
" Write-Host ' Added to TrustedHosts: ' $newHosts; " ^
|
||||
"} else { " ^
|
||||
" Write-Host ' TrustedHosts already configured'; " ^
|
||||
"}"
|
||||
|
||||
REM Step 5: Configure firewall rules
|
||||
echo [5/7] Configuring firewall rules...
|
||||
|
||||
REM Enable WinRM firewall rule for domain profile
|
||||
netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new enable=yes profile=domain >nul 2>&1
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
netsh advfirewall firewall add rule name="Windows Remote Management (HTTP-In)" dir=in action=allow protocol=tcp localport=5985 profile=domain >nul 2>&1
|
||||
)
|
||||
echo Firewall rule enabled for domain profile
|
||||
|
||||
REM Step 6: Set WinRM permissions for security group
|
||||
echo [6/7] Configuring WinRM permissions for security group...
|
||||
|
||||
powershell -ExecutionPolicy Bypass -Command ^
|
||||
"$group = '%SECURITY_GROUP%'; " ^
|
||||
"try { " ^
|
||||
" $ntAccount = New-Object System.Security.Principal.NTAccount($group); " ^
|
||||
" $sid = $ntAccount.Translate([System.Security.Principal.SecurityIdentifier]); " ^
|
||||
" $sidString = $sid.Value; " ^
|
||||
" Write-Host ' Group SID: ' $sidString; " ^
|
||||
" $currentSDDL = (Get-Item WSMan:\localhost\Service\RootSDDL).Value; " ^
|
||||
" $newACE = '(A;;GXGR;;;' + $sidString + ')'; " ^
|
||||
" if ($currentSDDL -notmatch [regex]::Escape($sidString)) { " ^
|
||||
" $newSDDL = $currentSDDL -replace 'D:', ('D:' + $newACE); " ^
|
||||
" Set-Item WSMan:\localhost\Service\RootSDDL -Value $newSDDL -Force; " ^
|
||||
" Write-Host ' Added security group to WinRM permissions'; " ^
|
||||
" } else { " ^
|
||||
" Write-Host ' Security group already has WinRM permissions'; " ^
|
||||
" } " ^
|
||||
"} catch { " ^
|
||||
" Write-Host ' ERROR: Could not resolve security group - ' $_.Exception.Message; " ^
|
||||
" exit 1; " ^
|
||||
"}"
|
||||
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo.
|
||||
echo ERROR: Failed to configure security group permissions.
|
||||
echo Verify the security group exists in Active Directory.
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Step 7: Log to CSV inventory file
|
||||
echo [7/7] Logging to inventory CSV...
|
||||
|
||||
REM Get IP address
|
||||
for /f "tokens=2 delims=:" %%a in ('ipconfig ^| findstr /i "IPv4"') do (
|
||||
set "IP_ADDRESS=%%a"
|
||||
goto :gotip
|
||||
)
|
||||
:gotip
|
||||
set "IP_ADDRESS=%IP_ADDRESS: =%"
|
||||
|
||||
REM Get current date/time
|
||||
for /f "tokens=2 delims==" %%a in ('wmic os get localdatetime /value') do set "DT=%%a"
|
||||
set "SETUP_DATE=%DT:~0,4%-%DT:~4,2%-%DT:~6,2% %DT:~8,2%:%DT:~10,2%:%DT:~12,2%"
|
||||
|
||||
REM Get OS version
|
||||
for /f "tokens=4-5 delims=. " %%a in ('ver') do set "OS_VERSION=%%a.%%b"
|
||||
|
||||
REM Create CSV directory if it doesn't exist
|
||||
if not exist "%LOG_PATH%" (
|
||||
mkdir "%LOG_PATH%" 2>nul
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo WARNING: Could not create log directory. Logging skipped.
|
||||
goto :skiplog
|
||||
)
|
||||
)
|
||||
|
||||
REM Define CSV file
|
||||
set "CSV_FILE=%LOG_PATH%\winrm-inventory.csv"
|
||||
|
||||
REM Create CSV header if file doesn't exist
|
||||
if not exist "%CSV_FILE%" (
|
||||
echo Hostname,IPAddress,SetupDate,OSVersion,SecurityGroup > "%CSV_FILE%"
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo WARNING: Could not create CSV file. Logging skipped.
|
||||
goto :skiplog
|
||||
)
|
||||
)
|
||||
|
||||
REM Check if this hostname already exists in CSV and update or append
|
||||
powershell -ExecutionPolicy Bypass -Command ^
|
||||
"$csvFile = '%CSV_FILE%'; " ^
|
||||
"$hostname = '%COMPUTERNAME%'; " ^
|
||||
"$newLine = '%COMPUTERNAME%,%IP_ADDRESS%,%SETUP_DATE%,%OS_VERSION%,%SECURITY_GROUP%'; " ^
|
||||
"try { " ^
|
||||
" $content = Get-Content $csvFile -ErrorAction SilentlyContinue; " ^
|
||||
" $found = $false; " ^
|
||||
" $newContent = @(); " ^
|
||||
" foreach ($line in $content) { " ^
|
||||
" if ($line -like \"$hostname,*\") { " ^
|
||||
" $newContent += $newLine; " ^
|
||||
" $found = $true; " ^
|
||||
" } else { " ^
|
||||
" $newContent += $line; " ^
|
||||
" } " ^
|
||||
" } " ^
|
||||
" if (-not $found) { $newContent += $newLine; } " ^
|
||||
" $newContent | Set-Content $csvFile -Force; " ^
|
||||
" Write-Host ' Logged: %COMPUTERNAME% (%IP_ADDRESS%)'; " ^
|
||||
"} catch { " ^
|
||||
" Write-Host ' WARNING: Could not write to CSV - ' $_.Exception.Message; " ^
|
||||
"}"
|
||||
|
||||
:skiplog
|
||||
|
||||
REM Verify configuration
|
||||
echo.
|
||||
echo ============================================================================
|
||||
echo WinRM Setup Complete!
|
||||
echo ============================================================================
|
||||
echo.
|
||||
echo Computer: %COMPUTERNAME%
|
||||
echo IP Address: %IP_ADDRESS%
|
||||
echo Security Group: %SECURITY_GROUP%
|
||||
echo WinRM Port: 5985 (HTTP)
|
||||
echo Trusted Hosts: %TRUSTED_HOSTS%
|
||||
echo.
|
||||
echo Inventory logged to: %CSV_FILE%
|
||||
echo.
|
||||
echo Members of '%SECURITY_GROUP%' can now connect using:
|
||||
echo Enter-PSSession -ComputerName %COMPUTERNAME% -Credential (Get-Credential)
|
||||
echo.
|
||||
echo To test from a remote PC (as a member of the security group):
|
||||
echo Test-WSMan -ComputerName %COMPUTERNAME%
|
||||
echo.
|
||||
echo ============================================================================
|
||||
|
||||
pause
|
||||
exit /b 0
|
||||
424
winrm-setup-package/WinRM-Setup-Guide.html
Normal file
424
winrm-setup-package/WinRM-Setup-Guide.html
Normal file
@@ -0,0 +1,424 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>WinRM Setup Package for Shopfloor PCs</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 1.6;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
color: #333;
|
||||
}
|
||||
h1 {
|
||||
color: #0078d4;
|
||||
border-bottom: 3px solid #0078d4;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
h2 {
|
||||
color: #106ebe;
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding-bottom: 5px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
h3 {
|
||||
color: #333;
|
||||
margin-top: 25px;
|
||||
}
|
||||
code {
|
||||
background: #e8e8e8;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Consolas', 'Courier New', monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
pre {
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
overflow-x: auto;
|
||||
font-family: 'Consolas', 'Courier New', monospace;
|
||||
font-size: 0.85em;
|
||||
line-height: 1.4;
|
||||
}
|
||||
pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 15px 0;
|
||||
background: white;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
th, td {
|
||||
padding: 10px 12px;
|
||||
text-align: left;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
th {
|
||||
background: #0078d4;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
tr:nth-child(even) { background: #f9f9f9; }
|
||||
tr:hover { background: #f0f7ff; }
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 1px solid #ddd;
|
||||
margin: 30px 0;
|
||||
}
|
||||
.warning {
|
||||
background: #fff3cd;
|
||||
border-left: 4px solid #ffc107;
|
||||
padding: 10px 15px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
.info {
|
||||
background: #e7f3ff;
|
||||
border-left: 4px solid #0078d4;
|
||||
padding: 10px 15px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
ol, ul { padding-left: 25px; }
|
||||
li { margin: 5px 0; }
|
||||
.toc {
|
||||
background: white;
|
||||
padding: 15px 20px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.toc h3 { margin-top: 0; }
|
||||
.toc ul { list-style: none; padding-left: 0; }
|
||||
.toc li { margin: 8px 0; }
|
||||
.toc a { color: #0078d4; text-decoration: none; }
|
||||
.toc a:hover { text-decoration: underline; }
|
||||
.comment { color: #6a9955; }
|
||||
.string { color: #ce9178; }
|
||||
.keyword { color: #569cd6; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>WinRM Setup Package for Shopfloor PCs</h1>
|
||||
|
||||
<p>This package provides scripts to configure WinRM (Windows Remote Management) on shopfloor PCs and execute remote maintenance tasks.</p>
|
||||
|
||||
<div class="toc">
|
||||
<h3>Contents</h3>
|
||||
<ul>
|
||||
<li><a href="#quick-start">Quick Start</a></li>
|
||||
<li><a href="#setup-details">Setup Script Details</a></li>
|
||||
<li><a href="#tasks">Available Remote Tasks</a></li>
|
||||
<li><a href="#troubleshooting">Troubleshooting</a></li>
|
||||
<li><a href="#security">Security Considerations</a></li>
|
||||
<li><a href="#custom-tasks">Adding Custom Tasks</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2>Package Contents</h2>
|
||||
|
||||
<table>
|
||||
<tr><th>File</th><th>Description</th></tr>
|
||||
<tr><td><code>Setup-WinRM.bat</code></td><td>Run on each PC to enable and configure WinRM</td></tr>
|
||||
<tr><td><code>Invoke-RemoteTask.ps1</code></td><td>PowerShell script to execute tasks on remote PCs</td></tr>
|
||||
<tr><td><code>hosts.txt</code></td><td>List of target computers (edit before use)</td></tr>
|
||||
<tr><td><code>WinRM-Setup-Guide.html</code></td><td>This documentation</td></tr>
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2 id="quick-start">Quick Start</h2>
|
||||
|
||||
<h3>Step 1: Configure Your Admin Workstation</h3>
|
||||
|
||||
<p>Before connecting to remote PCs, run this <strong>once</strong> on your admin workstation (as Administrator):</p>
|
||||
|
||||
<pre><code>Set-Item WSMan:\localhost\Client\TrustedHosts -Value <span class="string">"*.logon.ds.ge.com"</span> -Force</code></pre>
|
||||
|
||||
<p>This allows your workstation to connect to any PC in the domain.</p>
|
||||
|
||||
<h3>Step 2: Configure the Setup Script</h3>
|
||||
|
||||
<p>Edit <code>Setup-WinRM.bat</code> and update these values at the top:</p>
|
||||
|
||||
<pre><code><span class="comment">REM Default security group - who can use WinRM to connect</span>
|
||||
set "DEFAULT_SECURITY_GROUP=<span class="string">logon\groupid</span>"
|
||||
|
||||
<span class="comment">REM Where to log the inventory CSV (network share recommended)</span>
|
||||
set "DEFAULT_LOG_PATH=<span class="string">\\server\share\winrm-inventory</span>"
|
||||
|
||||
<span class="comment">REM Domain suffix for TrustedHosts</span>
|
||||
set "TRUSTED_DOMAIN=<span class="string">*.logon.ds.ge.com</span>"
|
||||
|
||||
<span class="comment">REM Optional: Trust a specific subnet (uncomment and set)</span>
|
||||
<span class="comment">REM set "TRUSTED_SUBNET=10.48.130.*"</span></code></pre>
|
||||
|
||||
<h3>Step 3: Create Security Group in Active Directory</h3>
|
||||
|
||||
<ol>
|
||||
<li>Open <strong>Active Directory Users and Computers</strong></li>
|
||||
<li>Create a new Security Group (or use existing group matching <code>groupid</code>)</li>
|
||||
<li>Add users who should have remote management access</li>
|
||||
</ol>
|
||||
|
||||
<h3>Step 4: Run Setup on Each Shopfloor PC</h3>
|
||||
|
||||
<p>Run as Administrator on each PC:</p>
|
||||
|
||||
<pre><code>Setup-WinRM.bat</code></pre>
|
||||
|
||||
<p>Or with parameters:</p>
|
||||
|
||||
<pre><code>Setup-WinRM.bat "logon\groupid" "\\server\share\winrm-inventory"</code></pre>
|
||||
|
||||
<p>The script will:</p>
|
||||
<ul>
|
||||
<li>Enable WinRM service</li>
|
||||
<li>Configure authentication (Negotiate/Kerberos)</li>
|
||||
<li>Set firewall rules (domain profile only)</li>
|
||||
<li>Restrict access to the security group</li>
|
||||
<li>Log hostname/IP to CSV inventory</li>
|
||||
</ul>
|
||||
|
||||
<h3>Step 5: Run Remote Tasks</h3>
|
||||
|
||||
<p>From your admin workstation, edit <code>hosts.txt</code> with target PCs, then:</p>
|
||||
|
||||
<pre><code><span class="comment"># Test connectivity</span>
|
||||
.\Invoke-RemoteTask.ps1 -Task TestConnection
|
||||
|
||||
<span class="comment"># Restart print spooler on all hosts</span>
|
||||
.\Invoke-RemoteTask.ps1 -Task RestartSpooler
|
||||
|
||||
<span class="comment"># Check disk space</span>
|
||||
.\Invoke-RemoteTask.ps1 -Task GetDiskSpace
|
||||
|
||||
<span class="comment"># Run on a single PC</span>
|
||||
.\Invoke-RemoteTask.ps1 -ComputerName <span class="string">"PC001"</span> -Task FlushDNS</code></pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2 id="setup-details">Setup Script Details</h2>
|
||||
|
||||
<h3>What Setup-WinRM.bat Configures</h3>
|
||||
|
||||
<table>
|
||||
<tr><th>Setting</th><th>Value</th><th>Purpose</th></tr>
|
||||
<tr><td>WinRM Service</td><td>Auto-start</td><td>Ensures WinRM starts on boot</td></tr>
|
||||
<tr><td>AllowUnencrypted</td><td>false</td><td>Security: require encrypted connections</td></tr>
|
||||
<tr><td>Negotiate Auth</td><td>true</td><td>Enables Kerberos/NTLM authentication</td></tr>
|
||||
<tr><td>CredSSP Auth</td><td>true</td><td>Enables credential delegation (double-hop)</td></tr>
|
||||
<tr><td>Firewall</td><td>Domain profile</td><td>Opens port 5985 for domain connections only</td></tr>
|
||||
<tr><td>TrustedHosts</td><td>*.logon.ds.ge.com</td><td>Trusts domain-joined PCs</td></tr>
|
||||
<tr><td>RootSDDL</td><td>Security group</td><td>Restricts who can connect</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>CSV Inventory</h3>
|
||||
|
||||
<p>The setup script logs each PC to a CSV file:</p>
|
||||
|
||||
<pre><code>Hostname,IPAddress,SetupDate,OSVersion,SecurityGroup
|
||||
PC001,10.48.130.101,2026-01-08 09:30:00,10.0,logon\groupid
|
||||
PC002,10.48.130.102,2026-01-08 09:35:00,10.0,logon\groupid</code></pre>
|
||||
|
||||
<p>You can use this CSV as your hosts file:</p>
|
||||
|
||||
<pre><code><span class="comment"># Extract hostnames from CSV</span>
|
||||
Import-Csv <span class="string">"\\server\share\winrm-inventory\winrm-inventory.csv"</span> |
|
||||
Select-Object -ExpandProperty Hostname |
|
||||
Set-Content .\hosts.txt</code></pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2 id="tasks">Available Remote Tasks</h2>
|
||||
|
||||
<table>
|
||||
<tr><th>Task</th><th>Description</th></tr>
|
||||
<tr><td><code>TestConnection</code></td><td>Verify WinRM connectivity</td></tr>
|
||||
<tr><td><code>GetUptime</code></td><td>Show system uptime and last boot time</td></tr>
|
||||
<tr><td><code>GetDiskSpace</code></td><td>Show free space on all drives</td></tr>
|
||||
<tr><td><code>RestartSpooler</code></td><td>Restart Print Spooler service</td></tr>
|
||||
<tr><td><code>FlushDNS</code></td><td>Clear DNS resolver cache</td></tr>
|
||||
<tr><td><code>ClearTempFiles</code></td><td>Delete Windows temp files</td></tr>
|
||||
<tr><td><code>DiskCleanup</code></td><td>Run Windows Disk Cleanup</td></tr>
|
||||
<tr><td><code>OptimizeDisk</code></td><td>TRIM (SSD) or Defrag (HDD)</td></tr>
|
||||
<tr><td><code>SyncTime</code></td><td>Force time sync with domain controller</td></tr>
|
||||
<tr><td><code>RestartService</code></td><td>Restart any Windows service (requires <code>-ServiceName</code>)</td></tr>
|
||||
<tr><td><code>RunCommand</code></td><td>Run custom PowerShell command (requires <code>-Command</code>)</td></tr>
|
||||
<tr><td><code>RestartComputer</code></td><td>Restart the remote PC (requires YES confirmation)</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>Examples</h3>
|
||||
|
||||
<pre><code><span class="comment"># Check uptime on all hosts</span>
|
||||
.\Invoke-RemoteTask.ps1 -Task GetUptime
|
||||
|
||||
<span class="comment"># Restart a specific service</span>
|
||||
.\Invoke-RemoteTask.ps1 -Task RestartService -ServiceName <span class="string">"Spooler"</span>
|
||||
|
||||
<span class="comment"># Run custom command</span>
|
||||
.\Invoke-RemoteTask.ps1 -Task RunCommand -Command <span class="string">"Get-Process | Sort CPU -Desc | Select -First 5"</span>
|
||||
|
||||
<span class="comment"># Use custom hosts file</span>
|
||||
.\Invoke-RemoteTask.ps1 -HostsFile <span class="string">".\cnc-machines.txt"</span> -Task FlushDNS
|
||||
|
||||
<span class="comment"># Specify DNS suffix for short hostnames</span>
|
||||
.\Invoke-RemoteTask.ps1 -DnsSuffix <span class="string">"logon.ds.ge.com"</span> -Task TestConnection
|
||||
|
||||
<span class="comment"># Restart a remote PC (will prompt for confirmation)</span>
|
||||
.\Invoke-RemoteTask.ps1 -ComputerName <span class="string">"PC001"</span> -Task RestartComputer
|
||||
|
||||
<span class="comment"># Increase parallelism for faster execution on many PCs</span>
|
||||
.\Invoke-RemoteTask.ps1 -Task FlushDNS -ThrottleLimit 20
|
||||
|
||||
<span class="comment"># Save results to a log file</span>
|
||||
.\Invoke-RemoteTask.ps1 -Task GetDiskSpace -LogResults</code></pre>
|
||||
|
||||
<h3>Targeting Multiple PCs</h3>
|
||||
|
||||
<pre><code><span class="comment"># Comma-separated list</span>
|
||||
.\Invoke-RemoteTask.ps1 -ComputerName <span class="string">"PC001"</span>,<span class="string">"PC002"</span>,<span class="string">"PC003"</span> -Task GetUptime
|
||||
|
||||
<span class="comment"># Array variable</span>
|
||||
$pcs = @(<span class="string">"PC001"</span>, <span class="string">"PC002"</span>, <span class="string">"PC003"</span>)
|
||||
.\Invoke-RemoteTask.ps1 -ComputerName $pcs -Task FlushDNS
|
||||
|
||||
<span class="comment"># From hosts.txt file (default)</span>
|
||||
.\Invoke-RemoteTask.ps1 -Task RestartSpooler
|
||||
|
||||
<span class="comment"># From CSV inventory</span>
|
||||
$pcs = (Import-Csv <span class="string">"\\server\share\winrm-inventory.csv"</span>).Hostname
|
||||
.\Invoke-RemoteTask.ps1 -ComputerName $pcs -Task GetDiskSpace
|
||||
|
||||
<span class="comment"># From Active Directory query</span>
|
||||
$pcs = (Get-ADComputer -Filter <span class="string">"Name -like 'SHOPFLOOR-*'"</span>).Name
|
||||
.\Invoke-RemoteTask.ps1 -ComputerName $pcs -Task SyncTime</code></pre>
|
||||
|
||||
<div class="info">
|
||||
<strong>Parallel Execution:</strong> All commands run in parallel (default: 10 concurrent connections). Adjust with <code>-ThrottleLimit</code> parameter.
|
||||
</div>
|
||||
|
||||
<h3>Logging Results</h3>
|
||||
|
||||
<p>Use <code>-LogResults</code> to save task output to a timestamped log file in the script directory:</p>
|
||||
|
||||
<pre><code>.\Invoke-RemoteTask.ps1 -Task RestartSpooler -LogResults
|
||||
<span class="comment"># Creates: RemoteTask_20260108_143022.log</span></code></pre>
|
||||
|
||||
<p>Log files contain:</p>
|
||||
<ul>
|
||||
<li>Task name and parameters</li>
|
||||
<li>Execution time</li>
|
||||
<li>Status of each computer (OK/FAIL)</li>
|
||||
<li>Result messages</li>
|
||||
<li>Summary totals</li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2 id="troubleshooting">Troubleshooting</h2>
|
||||
|
||||
<h3>"Access Denied" when connecting</h3>
|
||||
|
||||
<ol>
|
||||
<li>Verify you're a member of the WinRM security group</li>
|
||||
<li>Check that your credentials are correct</li>
|
||||
<li>Verify the target PC ran Setup-WinRM.bat successfully</li>
|
||||
</ol>
|
||||
|
||||
<h3>"WinRM cannot complete the operation"</h3>
|
||||
|
||||
<ol>
|
||||
<li>Verify the target PC is reachable: <code>ping PC001</code></li>
|
||||
<li>Check WinRM is running on target: <code>sc query winrm</code> (on target PC)</li>
|
||||
<li>Verify firewall allows port 5985</li>
|
||||
</ol>
|
||||
|
||||
<h3>"The WinRM client cannot process the request"</h3>
|
||||
|
||||
<p>Add target to TrustedHosts on your admin workstation:</p>
|
||||
|
||||
<pre><code>Set-Item WSMan:\localhost\Client\TrustedHosts -Value <span class="string">"*.logon.ds.ge.com"</span> -Force</code></pre>
|
||||
|
||||
<h3>Test WinRM Configuration</h3>
|
||||
|
||||
<p>On your admin workstation:</p>
|
||||
|
||||
<pre><code><span class="comment"># Test basic connectivity</span>
|
||||
Test-WSMan -ComputerName PC001
|
||||
|
||||
<span class="comment"># Test with credentials</span>
|
||||
$cred = Get-Credential
|
||||
Test-WSMan -ComputerName PC001 -Credential $cred -Authentication Negotiate
|
||||
|
||||
<span class="comment"># Enter interactive session</span>
|
||||
Enter-PSSession -ComputerName PC001 -Credential $cred -Authentication Negotiate</code></pre>
|
||||
|
||||
<p>On the target PC:</p>
|
||||
|
||||
<pre><code>winrm enumerate winrm/config/listener
|
||||
winrm get winrm/config/service</code></pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2 id="security">Security Considerations</h2>
|
||||
|
||||
<div class="warning">
|
||||
<strong>Important Security Notes:</strong>
|
||||
</div>
|
||||
|
||||
<ol>
|
||||
<li><strong>Use Security Groups</strong>: Always restrict WinRM access to a specific AD group</li>
|
||||
<li><strong>Domain Profile Only</strong>: Firewall rules only allow connections on domain networks</li>
|
||||
<li><strong>No Unencrypted Traffic</strong>: AllowUnencrypted is set to false</li>
|
||||
<li><strong>Audit Access</strong>: Enable Windows Security auditing for logon events</li>
|
||||
<li><strong>Credential Protection</strong>: Use dedicated admin accounts, not personal accounts</li>
|
||||
</ol>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2 id="custom-tasks">Adding Custom Tasks</h2>
|
||||
|
||||
<p>Edit <code>Invoke-RemoteTask.ps1</code> and add to the <code>$TaskScripts</code> hashtable:</p>
|
||||
|
||||
<pre><code><span class="string">'MyCustomTask'</span> = {
|
||||
$result = @{
|
||||
Success = <span class="keyword">$false</span>
|
||||
Hostname = $env:COMPUTERNAME
|
||||
Output = <span class="string">""</span>
|
||||
Error = <span class="keyword">$null</span>
|
||||
}
|
||||
<span class="keyword">try</span> {
|
||||
<span class="comment"># Your code here</span>
|
||||
$result.Output = <span class="string">"Task completed"</span>
|
||||
$result.Success = <span class="keyword">$true</span>
|
||||
} <span class="keyword">catch</span> {
|
||||
$result.Error = $_.Exception.Message
|
||||
}
|
||||
<span class="keyword">return</span> $result
|
||||
}</code></pre>
|
||||
|
||||
<p>Then add the task name to the <code>ValidateSet</code> in the param block.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Support</h2>
|
||||
|
||||
<p>For issues or questions, contact your IT support team.</p>
|
||||
|
||||
<p style="text-align: center; color: #666; margin-top: 40px; font-size: 0.9em;">
|
||||
WinRM Setup Package © 2026 | Generated from README.md
|
||||
</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
16
winrm-setup-package/hosts.txt
Normal file
16
winrm-setup-package/hosts.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
# WinRM Target Hosts
|
||||
# -------------------
|
||||
# Add one hostname or IP address per line
|
||||
# Lines starting with # are comments
|
||||
#
|
||||
# You can use:
|
||||
# - Hostnames: PC001, SHOPFLOOR-PC-01
|
||||
# - FQDNs: PC001.logon.ds.ge.com
|
||||
# - IP addresses: 10.48.130.100
|
||||
#
|
||||
# Example:
|
||||
# PC001
|
||||
# PC002
|
||||
# 10.48.130.100
|
||||
# shopfloor-cnc-01.logon.ds.ge.com
|
||||
|
||||
Reference in New Issue
Block a user