diff --git a/scripts/Invoke-RemoteAssetCollection-HTTPS.ps1 b/scripts/Invoke-RemoteAssetCollection-HTTPS.ps1 new file mode 100644 index 0000000..b1392d5 --- /dev/null +++ b/scripts/Invoke-RemoteAssetCollection-HTTPS.ps1 @@ -0,0 +1,559 @@ +#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 +}