#Requires -RunAsAdministrator <# .SYNOPSIS Remotely executes asset collection script on shopfloor PCs using WinRM. .DESCRIPTION This script uses WinRM to remotely execute the Update-PC-CompleteAsset.ps1 script on multiple shopfloor PCs. It handles: 1. WinRM configuration and trusted hosts setup 2. Credential management for remote connections 3. Parallel or sequential execution across multiple PCs 4. Error handling and logging for remote operations 5. Collection of results from each remote PC .PARAMETER ComputerList Array of computer names or IP addresses to collect data from. .PARAMETER ComputerListFile Path to a text file containing computer names/IPs (one per line). .PARAMETER Credential PSCredential object for authenticating to remote computers. If not provided, will prompt for credentials. .PARAMETER MaxConcurrent Maximum number of concurrent remote sessions (default: 5). .PARAMETER ProxyURL URL for the warranty proxy server (passed to remote script). .PARAMETER DashboardURL URL for the dashboard API (passed to remote script). .PARAMETER SkipWarranty Skip warranty lookups on remote PCs (passed to remote script). .PARAMETER LogPath Path for log files (default: .\logs\remote-collection.log). .PARAMETER TestConnections Test remote connections without running the full collection. .PARAMETER ScriptPath Path to the Update-PC-CompleteAsset.ps1 script on remote computers. Default: C:\Scripts\Update-PC-CompleteAsset.ps1 .EXAMPLE # Collect from specific computers with prompted credentials .\Invoke-RemoteAssetCollection.ps1 -ComputerList @("10.48.130.100", "10.48.130.101") .EXAMPLE # Collect from computers listed in file with stored credentials $cred = Get-Credential .\Invoke-RemoteAssetCollection.ps1 -ComputerListFile ".\shopfloor-pcs.txt" -Credential $cred .EXAMPLE # Test connections only .\Invoke-RemoteAssetCollection.ps1 -ComputerList @("10.48.130.100") -TestConnections .NOTES Author: System Administrator Date: 2025-09-26 Version: 1.0 Prerequisites: 1. WinRM must be enabled on target computers 2. PowerShell remoting must be enabled on target computers 3. Update-PC-CompleteAsset.ps1 must be present on target computers 4. Credentials with admin rights on target computers WinRM Setup Commands (run on management server): Enable-PSRemoting -Force Set-Item WSMan:\localhost\Client\TrustedHosts -Value "*" -Force WinRM Setup Commands (run on target computers): Enable-PSRemoting -Force Set-NetFirewallRule -DisplayName "Windows Remote Management (HTTP-In)" -Enabled True #> param( [Parameter(Mandatory=$false)] [string[]]$ComputerList = @(), [Parameter(Mandatory=$false)] [string]$ComputerListFile, [Parameter(Mandatory=$false)] [PSCredential]$Credential, [Parameter(Mandatory=$false)] [int]$MaxConcurrent = 5, [Parameter(Mandatory=$false)] [string]$ProxyURL = "http://10.48.130.158/vendor-api-proxy.php", [Parameter(Mandatory=$false)] [string]$DashboardURL = "https://tsgwp00525.rd.ds.ge.com/shopdb/api.asp", [Parameter(Mandatory=$false)] [switch]$SkipWarranty = $true, [Parameter(Mandatory=$false)] [string]$LogPath = ".\logs\remote-collection.log", [Parameter(Mandatory=$false)] [switch]$TestConnections = $false, [Parameter(Mandatory=$false)] [string]$ScriptPath = "C:\Scripts\Update-PC-CompleteAsset.ps1" ) # ============================================================================= # SSL/TLS Certificate Bypass for HTTPS connections # ============================================================================= # This allows connections to servers with self-signed or untrusted certificates try { # For PowerShell 5.x - use .NET callback 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 { # Silently continue if already set } # Set TLS 1.2 as minimum (required for modern HTTPS) [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 # Initialize logging function Initialize-Logging { param([string]$LogPath) $logDir = Split-Path $LogPath -Parent if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null } $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" Add-Content -Path $LogPath -Value "[$timestamp] Remote asset collection started" } function Write-Log { param([string]$Message, [string]$LogPath, [string]$Level = "INFO") $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $logEntry = "[$timestamp] [$Level] $Message" Add-Content -Path $LogPath -Value $logEntry switch ($Level) { "ERROR" { Write-Host $logEntry -ForegroundColor Red } "WARN" { Write-Host $logEntry -ForegroundColor Yellow } "SUCCESS" { Write-Host $logEntry -ForegroundColor Green } default { Write-Host $logEntry -ForegroundColor White } } } function Get-ComputerTargets { param([string[]]$ComputerList, [string]$ComputerListFile) $targets = @() # Add computers from direct list if ($ComputerList.Count -gt 0) { $targets += $ComputerList } # Add computers from file if (-not [string]::IsNullOrEmpty($ComputerListFile)) { if (Test-Path $ComputerListFile) { $fileTargets = Get-Content $ComputerListFile | Where-Object { $_.Trim() -ne "" -and -not $_.StartsWith("#") } $targets += $fileTargets } else { Write-Log "Computer list file not found: $ComputerListFile" $LogPath "ERROR" } } # Remove duplicates and return return ($targets | Sort-Object -Unique) } function Test-WinRMConnection { param([string]$ComputerName, [PSCredential]$Credential) try { $session = New-PSSession -ComputerName $ComputerName -Credential $Credential -ErrorAction Stop Remove-PSSession $session return $true } catch { return $false } } function Test-RemoteScriptExists { param([string]$ComputerName, [PSCredential]$Credential, [string]$ScriptPath) try { $result = Invoke-Command -ComputerName $ComputerName -Credential $Credential -ScriptBlock { param($Path) Test-Path $Path } -ArgumentList $ScriptPath return $result } catch { return $false } } function Invoke-RemoteAssetScript { param( [string]$ComputerName, [PSCredential]$Credential, [string]$ScriptPath, [string]$ProxyURL, [string]$DashboardURL, [bool]$SkipWarranty, [string]$LogPath ) try { Write-Log "Starting asset collection on $ComputerName" $LogPath "INFO" # Execute the script remotely $result = Invoke-Command -ComputerName $ComputerName -Credential $Credential -ScriptBlock { param($ScriptPath, $ProxyURL, $DashboardURL, $SkipWarranty) # Change to script directory $scriptDir = Split-Path $ScriptPath -Parent Set-Location $scriptDir # Build parameters $params = @{ ProxyURL = $ProxyURL DashboardURL = $DashboardURL } if ($SkipWarranty) { $params.SkipWarranty = $true } # Execute the script and capture output try { & $ScriptPath @params return @{ Success = $true Output = "Script completed successfully" Error = $null } } catch { return @{ Success = $false Output = $null Error = $_.Exception.Message } } } -ArgumentList $ScriptPath, $ProxyURL, $DashboardURL, $SkipWarranty if ($result.Success) { Write-Log "Asset collection completed successfully on $ComputerName" $LogPath "SUCCESS" # Update WinRM status in database - WinRM connection was successful # Get the actual hostname from the remote PC (in case we connected via IP) try { $remoteHostname = Invoke-Command -ComputerName $ComputerName -Credential $Credential -ScriptBlock { $env:COMPUTERNAME } -ErrorAction SilentlyContinue if ([string]::IsNullOrEmpty($remoteHostname)) { $remoteHostname = $ComputerName } $winrmBody = @{ action = "updateWinRMStatus" hostname = $remoteHostname hasWinRM = "1" } Write-Log "Updating WinRM status for hostname: $remoteHostname (connected as: $ComputerName)" $LogPath "INFO" $winrmResponse = Invoke-RestMethod -Uri $DashboardURL -Method Post -Body $winrmBody -ErrorAction Stop if ($winrmResponse.success) { Write-Log "WinRM status updated for $remoteHostname" $LogPath "INFO" } else { Write-Log "WinRM update response: $($winrmResponse | ConvertTo-Json -Compress)" $LogPath "WARN" } } catch { Write-Log "Failed to update WinRM status for $ComputerName - $($_.Exception.Message)" $LogPath "WARN" } return @{ Success = $true; Computer = $ComputerName; Message = $result.Output } } else { Write-Log "Asset collection failed on $ComputerName: $($result.Error)" $LogPath "ERROR" return @{ Success = $false; Computer = $ComputerName; Message = $result.Error } } } catch { $errorMsg = "Failed to execute on $ComputerName: $($_.Exception.Message)" Write-Log $errorMsg $LogPath "ERROR" return @{ Success = $false; Computer = $ComputerName; Message = $errorMsg } } } function Show-WinRMSetupInstructions { Write-Host "=== WinRM Setup Instructions ===" -ForegroundColor Cyan Write-Host "" Write-Host "On Management Server (this computer):" -ForegroundColor Yellow Write-Host " Enable-PSRemoting -Force" -ForegroundColor White Write-Host " Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force" -ForegroundColor White Write-Host "" Write-Host "On Target Computers:" -ForegroundColor Yellow Write-Host " Enable-PSRemoting -Force" -ForegroundColor White Write-Host " Set-NetFirewallRule -DisplayName 'Windows Remote Management (HTTP-In)' -Enabled True" -ForegroundColor White Write-Host "" Write-Host "For specific computer trust:" -ForegroundColor Yellow Write-Host " Set-Item WSMan:\localhost\Client\TrustedHosts -Value '10.48.130.100,10.48.130.101' -Force" -ForegroundColor White Write-Host "" } # Main execution try { Write-Host "=== Remote Asset Collection Script ===" -ForegroundColor Cyan Write-Host "Starting at $(Get-Date)" -ForegroundColor Gray Write-Host "" # Initialize logging Initialize-Logging -LogPath $LogPath # Get target computers $computers = Get-ComputerTargets -ComputerList $ComputerList -ComputerListFile $ComputerListFile if ($computers.Count -eq 0) { Write-Log "No target computers specified. Use -ComputerList or -ComputerListFile parameter." $LogPath "ERROR" Show-WinRMSetupInstructions exit 1 } Write-Log "Target computers: $($computers -join ', ')" $LogPath "INFO" # Get credentials if not provided if (-not $Credential) { Write-Host "Enter credentials for remote computer access:" -ForegroundColor Yellow $Credential = Get-Credential if (-not $Credential) { Write-Log "No credentials provided" $LogPath "ERROR" exit 1 } } # Test connections if requested if ($TestConnections) { Write-Host "`nTesting connections only..." -ForegroundColor Yellow foreach ($computer in $computers) { Write-Host "Testing $computer..." -NoNewline if (Test-WinRMConnection -ComputerName $computer -Credential $Credential) { Write-Host " [OK]" -ForegroundColor Green Write-Log "Connection test successful for $computer" $LogPath "SUCCESS" } else { Write-Host " [FAIL]" -ForegroundColor Red Write-Log "Connection test failed for $computer" $LogPath "ERROR" } } exit 0 } # Validate all connections and script existence before starting collection Write-Host "`nValidating remote connections and script availability..." -ForegroundColor Yellow $validComputers = @() foreach ($computer in $computers) { Write-Host "Validating $computer..." -NoNewline if (-not (Test-WinRMConnection -ComputerName $computer -Credential $Credential)) { Write-Host " [CONNECTION FAILED]" -ForegroundColor Red Write-Log "Cannot connect to $computer via WinRM" $LogPath "ERROR" continue } if (-not (Test-RemoteScriptExists -ComputerName $computer -Credential $Credential -ScriptPath $ScriptPath)) { Write-Host " [SCRIPT NOT FOUND]" -ForegroundColor Red Write-Log "Script not found on $computer at $ScriptPath" $LogPath "ERROR" continue } Write-Host " [OK]" -ForegroundColor Green $validComputers += $computer } if ($validComputers.Count -eq 0) { Write-Log "No valid computers found for data collection" $LogPath "ERROR" Show-WinRMSetupInstructions exit 1 } Write-Log "Valid computers for collection: $($validComputers -join ', ')" $LogPath "INFO" # Execute asset collection Write-Host "`nStarting asset collection on $($validComputers.Count) computers..." -ForegroundColor Cyan Write-Host "Max concurrent sessions: $MaxConcurrent" -ForegroundColor Gray Write-Host "" $results = @() $jobs = @() $completed = 0 # Process computers in batches for ($i = 0; $i -lt $validComputers.Count; $i += $MaxConcurrent) { $batch = $validComputers[$i..($i + $MaxConcurrent - 1)] Write-Host "Processing batch: $($batch -join ', ')" -ForegroundColor Yellow # Start jobs for current batch foreach ($computer in $batch) { $job = Start-Job -ScriptBlock { param($Computer, $Credential, $ScriptPath, $ProxyURL, $DashboardURL, $SkipWarranty, $LogPath, $Functions) # Import functions into job scope Invoke-Expression $Functions Invoke-RemoteAssetScript -ComputerName $Computer -Credential $Credential ` -ScriptPath $ScriptPath -ProxyURL $ProxyURL -DashboardURL $DashboardURL ` -SkipWarranty $SkipWarranty -LogPath $LogPath } -ArgumentList $computer, $Credential, $ScriptPath, $ProxyURL, $DashboardURL, $SkipWarranty, $LogPath, (Get-Content $PSCommandPath | Out-String) $jobs += $job } # Wait for batch to complete $jobs | Wait-Job | Out-Null # Collect results foreach ($job in $jobs) { $result = Receive-Job $job $results += $result Remove-Job $job $completed++ $computer = $result.Computer if ($result.Success) { Write-Host "[✓] $computer - Completed successfully" -ForegroundColor Green } else { Write-Host "[✗] $computer - Failed: $($result.Message)" -ForegroundColor Red } } $jobs = @() Write-Host "Batch completed. Progress: $completed/$($validComputers.Count)" -ForegroundColor Gray Write-Host "" } # Summary $successful = ($results | Where-Object { $_.Success }).Count $failed = ($results | Where-Object { -not $_.Success }).Count Write-Host "=== Collection Summary ===" -ForegroundColor Cyan Write-Host "Total computers: $($validComputers.Count)" -ForegroundColor White Write-Host "Successful: $successful" -ForegroundColor Green Write-Host "Failed: $failed" -ForegroundColor Red if ($failed -gt 0) { Write-Host "`nFailed computers:" -ForegroundColor Yellow $results | Where-Object { -not $_.Success } | ForEach-Object { Write-Host " $($_.Computer): $($_.Message)" -ForegroundColor Red } } Write-Log "Collection completed. Success: $successful, Failed: $failed" $LogPath "INFO" } catch { Write-Log "Fatal error: $($_.Exception.Message)" $LogPath "ERROR" exit 1 }