#Requires -RunAsAdministrator <# .SYNOPSIS Remotely executes asset collection script on shopfloor PCs using WinRM over HTTPS. .DESCRIPTION This script uses WinRM HTTPS to securely execute the Update-PC-CompleteAsset.ps1 script on multiple shopfloor PCs. It handles: 1. Secure HTTPS connections using wildcard certificates 2. Automatic FQDN resolution from hostnames 3. Credential management for remote connections 4. Parallel execution across multiple PCs 5. Error handling and logging for remote operations 6. Collection of results from each remote PC .PARAMETER HostnameList Array of computer hostnames (without domain suffix). .PARAMETER HostnameListFile Path to a text file containing hostnames (one per line, without domain suffix). .PARAMETER Domain Domain suffix for FQDNs (e.g., "logon.ds.ge.com"). Will construct FQDNs as: hostname.domain .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 Port HTTPS port for WinRM (default: 5986). .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-https.log). .PARAMETER TestConnections Test remote HTTPS 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 .PARAMETER SkipCertificateCheck Skip SSL certificate validation (not recommended for production). .EXAMPLE # Collect from specific hostnames .\Invoke-RemoteAssetCollection-HTTPS.ps1 -HostnameList @("PC001", "PC002") -Domain "logon.ds.ge.com" .EXAMPLE # Collect from hostnames in file .\Invoke-RemoteAssetCollection-HTTPS.ps1 -HostnameListFile ".\shopfloor-hostnames.txt" -Domain "logon.ds.ge.com" .EXAMPLE # Test HTTPS connections only .\Invoke-RemoteAssetCollection-HTTPS.ps1 -HostnameList @("PC001") -Domain "logon.ds.ge.com" -TestConnections .EXAMPLE # Use stored credentials $cred = Get-Credential .\Invoke-RemoteAssetCollection-HTTPS.ps1 -HostnameListFile ".\shopfloor-hostnames.txt" ` -Domain "logon.ds.ge.com" -Credential $cred .NOTES Author: System Administrator Date: 2025-10-17 Version: 1.0 Prerequisites: 1. WinRM HTTPS must be configured on target computers (use Setup-WinRM-HTTPS.ps1) 2. Wildcard certificate installed on target computers 3. PowerShell 5.1 or later 4. Update-PC-CompleteAsset.ps1 must be present on target computers 5. Credentials with admin rights on target computers 6. Network connectivity to target computers on port 5986 Advantages over HTTP WinRM: - Encrypted traffic (credentials and data) - No TrustedHosts configuration required - Better security posture for production environments #> param( [Parameter(Mandatory=$false)] [string[]]$HostnameList = @(), [Parameter(Mandatory=$false)] [string]$HostnameListFile, [Parameter(Mandatory=$true)] [string]$Domain, [Parameter(Mandatory=$false)] [PSCredential]$Credential, [Parameter(Mandatory=$false)] [int]$MaxConcurrent = 5, [Parameter(Mandatory=$false)] [int]$Port = 5986, [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-https.log", [Parameter(Mandatory=$false)] [switch]$TestConnections = $false, [Parameter(Mandatory=$false)] [string]$ScriptPath = "C:\Scripts\Update-PC-CompleteAsset.ps1", [Parameter(Mandatory=$false)] [switch]$SkipCertificateCheck = $false ) # ============================================================================= # SSL/TLS Certificate Bypass for HTTPS connections # ============================================================================= 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 # 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 (HTTPS) 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[]]$HostnameList, [string]$HostnameListFile, [string]$Domain) $hostnames = @() # Add hostnames from direct list if ($HostnameList.Count -gt 0) { $hostnames += $HostnameList } # Add hostnames from file if (-not [string]::IsNullOrEmpty($HostnameListFile)) { if (Test-Path $HostnameListFile) { $fileHostnames = Get-Content $HostnameListFile | Where-Object { $_.Trim() -ne "" -and -not $_.StartsWith("#") } | ForEach-Object { $_.Trim() } $hostnames += $fileHostnames } else { Write-Log "Hostname list file not found: $HostnameListFile" $LogPath "ERROR" } } # Remove duplicates and construct FQDNs $fqdns = $hostnames | Sort-Object -Unique | ForEach-Object { $hostname = $_.Trim() # Remove domain if already present if ($hostname -like "*.$Domain") { $hostname } else { "$hostname.$Domain" } } return $fqdns } function Resolve-ComputerIP { param([string]$FQDN) try { $result = Resolve-DnsName -Name $FQDN -Type A -ErrorAction Stop if ($result -and $result[0].IPAddress) { return $result[0].IPAddress } return $null } catch { return $null } } function Test-WinRMHTTPSConnection { param([string]$ComputerName, [PSCredential]$Credential, [int]$Port, [bool]$SkipCertCheck) try { $sessionOptions = New-PSSessionOption -SkipCACheck:$SkipCertCheck -SkipCNCheck:$SkipCertCheck $session = New-PSSession -ComputerName $ComputerName ` -Credential $Credential ` -UseSSL ` -Port $Port ` -SessionOption $sessionOptions ` -ErrorAction Stop Remove-PSSession $session return $true } catch { return $false } } function Test-RemoteScriptExists { param([string]$ComputerName, [PSCredential]$Credential, [string]$ScriptPath, [int]$Port, [bool]$SkipCertCheck) try { $sessionOptions = New-PSSessionOption -SkipCACheck:$SkipCertCheck -SkipCNCheck:$SkipCertCheck $result = Invoke-Command -ComputerName $ComputerName ` -Credential $Credential ` -UseSSL ` -Port $Port ` -SessionOption $sessionOptions ` -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, [int]$Port, [bool]$SkipCertCheck, [string]$LogPath ) try { Write-Log "Starting asset collection on $ComputerName (HTTPS)" $LogPath "INFO" $sessionOptions = New-PSSessionOption -SkipCACheck:$SkipCertCheck -SkipCNCheck:$SkipCertCheck # Execute the script remotely $result = Invoke-Command -ComputerName $ComputerName ` -Credential $Credential ` -UseSSL ` -Port $Port ` -SessionOption $sessionOptions ` -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" 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-SetupInstructions { param([string]$Domain) Write-Host "`n=== WinRM HTTPS Setup Instructions ===" -ForegroundColor Cyan Write-Host "" Write-Host "On each target computer, run the Setup-WinRM-HTTPS.ps1 script:" -ForegroundColor Yellow Write-Host "" Write-Host " # With certificate PFX file:" -ForegroundColor Gray Write-Host " `$certPass = ConvertTo-SecureString 'Password' -AsPlainText -Force" -ForegroundColor White Write-Host " .\Setup-WinRM-HTTPS.ps1 -CertificatePath 'C:\Certs\wildcard.pfx' ``" -ForegroundColor White Write-Host " -CertificatePassword `$certPass -Domain '$Domain'" -ForegroundColor White Write-Host "" Write-Host " # Or with existing certificate:" -ForegroundColor Gray Write-Host " .\Setup-WinRM-HTTPS.ps1 -CertificateThumbprint 'THUMBPRINT' -Domain '$Domain'" -ForegroundColor White Write-Host "" Write-Host "This will:" -ForegroundColor Yellow Write-Host " 1. Install/locate the wildcard certificate" -ForegroundColor White Write-Host " 2. Create HTTPS listener on port 5986" -ForegroundColor White Write-Host " 3. Configure Windows Firewall" -ForegroundColor White Write-Host " 4. Enable WinRM service" -ForegroundColor White Write-Host "" } # Main execution try { Write-Host "=== Remote Asset Collection Script (HTTPS) ===" -ForegroundColor Cyan Write-Host "Starting at $(Get-Date)" -ForegroundColor Gray Write-Host "" # Initialize logging Initialize-Logging -LogPath $LogPath # Get target computers $fqdns = Get-ComputerTargets -HostnameList $HostnameList -HostnameListFile $HostnameListFile -Domain $Domain if ($fqdns.Count -eq 0) { Write-Log "No target computers specified. Use -HostnameList or -HostnameListFile parameter." $LogPath "ERROR" Show-SetupInstructions -Domain $Domain exit 1 } Write-Log "Target computers (FQDNs): $($fqdns -join ', ')" $LogPath "INFO" # Resolve IP addresses Write-Host "`nResolving IP addresses..." -ForegroundColor Yellow $resolvedComputers = @() foreach ($fqdn in $fqdns) { Write-Host "Resolving $fqdn..." -NoNewline $ip = Resolve-ComputerIP -FQDN $fqdn if ($ip) { Write-Host " [$ip]" -ForegroundColor Green $resolvedComputers += @{ FQDN = $fqdn; IP = $ip } Write-Log "Resolved $fqdn to $ip" $LogPath "INFO" } else { Write-Host " [DNS FAILED]" -ForegroundColor Red Write-Log "Failed to resolve $fqdn" $LogPath "WARN" } } if ($resolvedComputers.Count -eq 0) { Write-Log "No computers could be resolved via DNS" $LogPath "ERROR" exit 1 } # Get credentials if not provided if (-not $Credential) { Write-Host "`nEnter 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 HTTPS connections only..." -ForegroundColor Yellow foreach ($comp in $resolvedComputers) { $fqdn = $comp.FQDN Write-Host "Testing $fqdn..." -NoNewline if (Test-WinRMHTTPSConnection -ComputerName $fqdn -Credential $Credential -Port $Port -SkipCertCheck $SkipCertificateCheck) { Write-Host " [OK]" -ForegroundColor Green Write-Log "HTTPS connection test successful for $fqdn" $LogPath "SUCCESS" } else { Write-Host " [FAIL]" -ForegroundColor Red Write-Log "HTTPS connection test failed for $fqdn" $LogPath "ERROR" } } exit 0 } # Validate all connections and script existence before starting collection Write-Host "`nValidating remote HTTPS connections and script availability..." -ForegroundColor Yellow $validComputers = @() foreach ($comp in $resolvedComputers) { $fqdn = $comp.FQDN Write-Host "Validating $fqdn..." -NoNewline if (-not (Test-WinRMHTTPSConnection -ComputerName $fqdn -Credential $Credential -Port $Port -SkipCertCheck $SkipCertificateCheck)) { Write-Host " [CONNECTION FAILED]" -ForegroundColor Red Write-Log "Cannot connect to $fqdn via WinRM HTTPS" $LogPath "ERROR" continue } if (-not (Test-RemoteScriptExists -ComputerName $fqdn -Credential $Credential -ScriptPath $ScriptPath -Port $Port -SkipCertCheck $SkipCertificateCheck)) { Write-Host " [SCRIPT NOT FOUND]" -ForegroundColor Red Write-Log "Script not found on $fqdn at $ScriptPath" $LogPath "ERROR" continue } Write-Host " [OK]" -ForegroundColor Green $validComputers += $comp } if ($validComputers.Count -eq 0) { Write-Log "No valid computers found for data collection" $LogPath "ERROR" Show-SetupInstructions -Domain $Domain exit 1 } Write-Log "Valid computers for collection: $($validComputers.FQDN -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 "Using HTTPS on port: $Port" -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.FQDN -join ', ')" -ForegroundColor Yellow # Start jobs for current batch foreach ($comp in $batch) { $fqdn = $comp.FQDN $job = Start-Job -ScriptBlock { param($FQDN, $Credential, $ScriptPath, $ProxyURL, $DashboardURL, $SkipWarranty, $Port, $SkipCertCheck, $LogPath, $Functions) # Import functions into job scope Invoke-Expression $Functions Invoke-RemoteAssetScript -ComputerName $FQDN -Credential $Credential ` -ScriptPath $ScriptPath -ProxyURL $ProxyURL -DashboardURL $DashboardURL ` -SkipWarranty $SkipWarranty -Port $Port -SkipCertCheck $SkipCertCheck -LogPath $LogPath } -ArgumentList $fqdn, $Credential, $ScriptPath, $ProxyURL, $DashboardURL, $SkipWarranty, $Port, $SkipCertificateCheck, $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 "[OK] $computer - Completed successfully" -ForegroundColor Green } else { Write-Host "[FAIL] $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 }