From 179d39eb997e23828ba8c3fa5c02937ba93e2c5f Mon Sep 17 00:00:00 2001 From: cproudlock Date: Tue, 9 Dec 2025 13:12:49 -0500 Subject: [PATCH] Update PowerShell scripts for production API and SSL bypass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Changed Invoke-RemoteAssetCollection.ps1 default URL to production: https://tsgwp00525.rd.ds.ge.com/shopdb/api.asp - Added SSL/TLS certificate bypass for HTTPS connections in both: - Update-PC-CompleteAsset.ps1 - Invoke-RemoteAssetCollection.ps1 - Set TLS 1.2 as minimum protocol version - Security group logon\g03078610 confirmed correct 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- scripts/Get-ShopfloorConfig.ps1 | 480 ++++++++ scripts/Invoke-RemoteAssetCollection.ps1 | 484 ++++++++ scripts/Update-PC-CompleteAsset.ps1 | 1384 ++++++++++++++++++++++ 3 files changed, 2348 insertions(+) create mode 100644 scripts/Get-ShopfloorConfig.ps1 create mode 100644 scripts/Invoke-RemoteAssetCollection.ps1 create mode 100644 scripts/Update-PC-CompleteAsset.ps1 diff --git a/scripts/Get-ShopfloorConfig.ps1 b/scripts/Get-ShopfloorConfig.ps1 new file mode 100644 index 0000000..35f21ef --- /dev/null +++ b/scripts/Get-ShopfloorConfig.ps1 @@ -0,0 +1,480 @@ +# Functions to collect shopfloor PC network and communication configurations + +# Function to get all network interfaces and their configurations +function Get-NetworkInterfaceConfig { + Write-Host " Collecting network interface information..." -ForegroundColor Yellow + + $interfaces = @() + + try { + # Get all network adapters with IP configurations + $adapters = Get-NetAdapter | Where-Object { $_.Status -eq 'Up' } + + foreach ($adapter in $adapters) { + $ipConfig = Get-NetIPConfiguration -InterfaceIndex $adapter.ifIndex -ErrorAction SilentlyContinue + + if ($ipConfig -and $ipConfig.IPv4Address) { + foreach ($ip in $ipConfig.IPv4Address) { + $gateway = if ($ipConfig.IPv4DefaultGateway) { $ipConfig.IPv4DefaultGateway[0].NextHop } else { $null } + + # Determine if this is a machine network (192.168.*.*) + $isMachineNetwork = $ip.IPAddress -match '^192\.168\.' + + $interface = @{ + InterfaceName = $adapter.Name + IPAddress = $ip.IPAddress + SubnetMask = $ip.PrefixLength # Will need conversion + DefaultGateway = $gateway + MACAddress = $adapter.MacAddress + IsDHCP = if ($ipConfig.NetIPv4Interface.Dhcp -eq 'Enabled') { 1 } else { 0 } + IsActive = 1 + IsMachineNetwork = if ($isMachineNetwork) { 1 } else { 0 } + } + + $interfaces += $interface + + if ($isMachineNetwork) { + Write-Host " Found machine network: $($ip.IPAddress) on $($adapter.Name)" -ForegroundColor Cyan + } + else { + Write-Host " Found network: $($ip.IPAddress) on $($adapter.Name)" -ForegroundColor Gray + } + } + } + } + } + catch { + Write-Host " Error collecting network info: $_" -ForegroundColor Red + } + + # Alternative method using WMI if NetAdapter cmdlets fail + if ($interfaces.Count -eq 0) { + try { + $wmiAdapters = Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object { $_.IPEnabled -eq $true } + + foreach ($adapter in $wmiAdapters) { + if ($adapter.IPAddress) { + foreach ($i in 0..($adapter.IPAddress.Count - 1)) { + $ip = $adapter.IPAddress[$i] + + # Skip IPv6 addresses + if ($ip -match ':') { continue } + + $isMachineNetwork = $ip -match '^192\.168\.' + + $interface = @{ + InterfaceName = $adapter.Description + IPAddress = $ip + SubnetMask = $adapter.IPSubnet[$i] + DefaultGateway = if ($adapter.DefaultIPGateway) { $adapter.DefaultIPGateway[0] } else { $null } + MACAddress = $adapter.MACAddress + IsDHCP = $adapter.DHCPEnabled + IsActive = 1 + IsMachineNetwork = if ($isMachineNetwork) { 1 } else { 0 } + } + + $interfaces += $interface + + if ($isMachineNetwork) { + Write-Host " Found machine network: $ip on $($adapter.Description)" -ForegroundColor Cyan + } + } + } + } + } + catch { + Write-Host " WMI method also failed: $_" -ForegroundColor Red + } + } + + return $interfaces +} + +# Function to get serial port configurations from registry +function Get-SerialPortConfig { + Write-Host " Collecting serial port configurations..." -ForegroundColor Yellow + + $configs = @() + + # Registry paths to check + $registryPaths = @{ + 'Serial' = @('HKLM:\SOFTWARE\GE Aircraft Engines\DNC\Serial', 'HKLM:\SOFTWARE\WOW6432Node\GE Aircraft Engines\DNC\Serial') + 'Mark' = @('HKLM:\SOFTWARE\GE Aircraft Engines\DNC\Mark', 'HKLM:\SOFTWARE\WOW6432Node\GE Aircraft Engines\DNC\Mark') + 'PPDCS' = @('HKLM:\SOFTWARE\GE Aircraft Engines\DNC\PPDCS', 'HKLM:\SOFTWARE\WOW6432Node\GE Aircraft Engines\DNC\PPDCS') + 'TQM9030' = @('HKLM:\SOFTWARE\GE Aircraft Engines\DNC\TQM9030', 'HKLM:\SOFTWARE\WOW6432Node\GE Aircraft Engines\DNC\TQM9030') + 'TQMCaron' = @('HKLM:\SOFTWARE\GE Aircraft Engines\DNC\TQMCaron', 'HKLM:\SOFTWARE\WOW6432Node\GE Aircraft Engines\DNC\TQMCaron') + } + + foreach ($configType in $registryPaths.Keys) { + foreach ($path in $registryPaths[$configType]) { + if (Test-Path $path) { + try { + $regValues = Get-ItemProperty -Path $path -ErrorAction SilentlyContinue + + if ($regValues) { + $config = @{ + ConfigType = $configType + PortID = $regValues.'Port Id' -replace 'Port Id2', '' + Baud = $regValues.Baud + DataBits = $regValues.'Data Bits' + StopBits = $regValues.'Stop Bits' + Parity = $regValues.Parity + CRLF = $regValues.CRLF + IPAddress = $null + SocketNo = $null + AdditionalSettings = @{} + } + + # Collect any additional settings + $standardKeys = @('Port Id', 'Baud', 'Data Bits', 'Stop Bits', 'Parity', 'CRLF', 'PSPath', 'PSParentPath', 'PSChildName', 'PSProvider') + foreach ($prop in $regValues.PSObject.Properties) { + if ($prop.Name -notin $standardKeys -and $prop.Value) { + $config.AdditionalSettings[$prop.Name] = $prop.Value + } + } + + # Convert additional settings to JSON + if ($config.AdditionalSettings.Count -gt 0) { + $config.AdditionalSettings = $config.AdditionalSettings | ConvertTo-Json -Compress + } + else { + $config.AdditionalSettings = $null + } + + if ($config.PortID) { + $configs += $config + Write-Host " Found $configType config: Port $($config.PortID), Baud $($config.Baud)" -ForegroundColor Cyan + } + } + } + catch { + Write-Host " Error reading $configType registry: $_" -ForegroundColor Red + } + } + } + } + + # Check for eFocas configuration (network-based) + $efocasPaths = @('HKLM:\SOFTWARE\GE Aircraft Engines\DNC\eFocas', 'HKLM:\SOFTWARE\WOW6432Node\GE Aircraft Engines\DNC\eFocas') + foreach ($path in $efocasPaths) { + if (Test-Path $path) { + try { + $regValues = Get-ItemProperty -Path $path -ErrorAction SilentlyContinue + + if ($regValues -and $regValues.IpAddr) { + $config = @{ + ConfigType = 'eFocas' + PortID = $null + Baud = $null + DataBits = $null + StopBits = $null + Parity = $null + CRLF = $null + IPAddress = $regValues.IpAddr + SocketNo = $regValues.SocketNo + AdditionalSettings = @{ + DualPath = $regValues.DualPath + Path1Name = $regValues.Path1Name + Path2Name = $regValues.Path2Name + Danobat = $regValues.Danobat + DataServer = $regValues.DataServer + } | ConvertTo-Json -Compress + } + + $configs += $config + Write-Host " Found eFocas config: IP $($config.IPAddress), Socket $($config.SocketNo)" -ForegroundColor Cyan + } + } + catch { + Write-Host " Error reading eFocas registry: $_" -ForegroundColor Red + } + } + } + + return $configs +} + +# Function to get GE Aircraft Engines registry information and DualPath configuration +function Get-GERegistryInfo { + Write-Host " Collecting GE Aircraft Engines registry information..." -ForegroundColor Yellow + + $geInfo = @{ + Registry32Bit = $false + Registry64Bit = $false + DualPathEnabled = $null + Path1Name = $null + Path2Name = $null + RegistryNotes = @{} + } + + # Check both 32-bit and 64-bit registry paths + $registryPaths = @{ + '32bit' = 'HKLM:\SOFTWARE\GE Aircraft Engines' + '64bit' = 'HKLM:\SOFTWARE\WOW6432Node\GE Aircraft Engines' + } + + foreach ($pathType in $registryPaths.Keys) { + $basePath = $registryPaths[$pathType] + Write-Host " Checking $pathType registry: $basePath" -ForegroundColor Gray + + if (Test-Path $basePath) { + Write-Host " [FOUND] GE Aircraft Engines in $pathType registry" -ForegroundColor Green + + if ($pathType -eq '32bit') { + $geInfo.Registry32Bit = $true + } else { + $geInfo.Registry64Bit = $true + } + + # Collect information about what's under GE Aircraft Engines + try { + $subKeys = Get-ChildItem -Path $basePath -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name | ForEach-Object { Split-Path $_ -Leaf } + $geInfo.RegistryNotes[$pathType] = @{ + BasePath = $basePath + SubKeys = $subKeys -join ', ' + Found = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + } + Write-Host " Sub-keys found: $($subKeys -join ', ')" -ForegroundColor Cyan + } + catch { + Write-Host " Error reading sub-keys: $_" -ForegroundColor Red + $geInfo.RegistryNotes[$pathType] = @{ + Error = $_.Exception.Message + } + } + + # Check for DualPath configuration in eFocas + $efocasPath = "$basePath\DNC\eFocas" + if (Test-Path $efocasPath) { + Write-Host " Checking eFocas configuration for DualPath..." -ForegroundColor Yellow + try { + $efocasValues = Get-ItemProperty -Path $efocasPath -ErrorAction SilentlyContinue + + if ($efocasValues.DualPath) { + Write-Host " [FOUND] DualPath = $($efocasValues.DualPath)" -ForegroundColor Green + + # Handle case where both registry locations exist - prioritize settings from first found + if ($geInfo.DualPathEnabled -eq $null) { + $geInfo.DualPathEnabled = $efocasValues.DualPath -eq 'YES' + Write-Host " Setting DualPath from $pathType registry: $($geInfo.DualPathEnabled)" -ForegroundColor Cyan + } else { + Write-Host " DualPath already set from other registry location, keeping existing value" -ForegroundColor Yellow + } + + # Handle Path1Name - use first non-empty value found + if (!$geInfo.Path1Name -and $efocasValues.Path1Name) { + $geInfo.Path1Name = $efocasValues.Path1Name + Write-Host " Path1Name = $($efocasValues.Path1Name)" -ForegroundColor Cyan + } + + # Handle Path2Name - use first non-empty value found + if (!$geInfo.Path2Name -and $efocasValues.Path2Name) { + $geInfo.Path2Name = $efocasValues.Path2Name + Write-Host " Path2Name = $($efocasValues.Path2Name)" -ForegroundColor Cyan + } + + # Store additional eFocas settings + $geInfo.RegistryNotes["$pathType-eFocas"] = @{ + DualPath = $efocasValues.DualPath + Path1Name = $efocasValues.Path1Name + Path2Name = $efocasValues.Path2Name + IpAddr = $efocasValues.IpAddr + SocketNo = $efocasValues.SocketNo + Danobat = $efocasValues.Danobat + DataServer = $efocasValues.DataServer + } + } + } + catch { + Write-Host " Error reading eFocas configuration: $_" -ForegroundColor Red + } + } + } else { + Write-Host " [NOT FOUND] No GE Aircraft Engines in $pathType registry" -ForegroundColor Gray + } + } + + # Summary + Write-Host " GE Registry Summary:" -ForegroundColor Green + Write-Host " 32-bit registry: $(if ($geInfo.Registry32Bit) { 'YES' } else { 'NO' })" -ForegroundColor Cyan + Write-Host " 64-bit registry: $(if ($geInfo.Registry64Bit) { 'YES' } else { 'NO' })" -ForegroundColor Cyan + Write-Host " DualPath enabled: $(if ($geInfo.DualPathEnabled -eq $null) { 'NOT FOUND' } elseif ($geInfo.DualPathEnabled) { 'YES' } else { 'NO' })" -ForegroundColor Cyan + if ($geInfo.Path1Name) { Write-Host " Path1Name: $($geInfo.Path1Name)" -ForegroundColor Cyan } + if ($geInfo.Path2Name) { Write-Host " Path2Name: $($geInfo.Path2Name)" -ForegroundColor Cyan } + + return $geInfo +} + +# Function to get DNC configuration from registry +function Get-DNCConfig { + Write-Host " Collecting DNC configuration..." -ForegroundColor Yellow + + $dncConfig = $null + $paths = @('HKLM:\SOFTWARE\GE Aircraft Engines\DNC\General', 'HKLM:\SOFTWARE\WOW6432Node\GE Aircraft Engines\DNC\General') + + Write-Host " Checking registry paths for DNC config..." -ForegroundColor Gray + foreach ($path in $paths) { + Write-Host " Checking path: $path" -ForegroundColor Gray + if (Test-Path $path) { + Write-Host " Path exists! Reading values..." -ForegroundColor Green + try { + $general = Get-ItemProperty -Path $path -ErrorAction SilentlyContinue + $mxPath = $path -replace 'General', 'MX' + $mx = Get-ItemProperty -Path $mxPath -ErrorAction SilentlyContinue + $fmsPath = $path -replace 'General', 'FMS' + $fms = Get-ItemProperty -Path $fmsPath -ErrorAction SilentlyContinue + + if ($general) { + Write-Host " Found General config values!" -ForegroundColor Green + $dncConfig = @{ + Site = $general.Site + CNC = $general.Cnc + NcIF = $general.NcIF + MachineNo = $general.MachineNo + HostType = $general.HostType + FtpHostPrimary = if ($mx) { $mx.FtpHostPrimary } else { $null } + FtpHostSecondary = if ($mx) { $mx.FtpHostSecondary } else { $null } + FtpAccount = if ($mx) { $mx.FtpAccount } else { $null } + Debug = $general.Debug + Uploads = $general.Uploads + Scanner = $general.Scanner + Dripfeed = $general.Dripfeed + AdditionalSettings = @{ + Mode = $general.Mode + 'Unit/Area' = $general.'Unit/Area' + DvUpldDir = $general.DvUpldDir + Ncedt = $general.Ncedt + Maint = $general.Maint + ChangeWorkstation = $general.ChangeWorkstation + FMSHostPrimary = if ($fms) { $fms.FMSHostPrimary } else { $null } + FMSHostSecondary = if ($fms) { $fms.FMSHostSecondary } else { $null } + } | ConvertTo-Json -Compress + } + + Write-Host " Found DNC config: Site=$($dncConfig.Site), MachineNo=$($dncConfig.MachineNo), CNC=$($dncConfig.CNC)" -ForegroundColor Cyan + Write-Host " DNC Config JSON: $($dncConfig | ConvertTo-Json -Compress)" -ForegroundColor Gray + break + } + } + catch { + Write-Host " Error reading DNC registry: $_" -ForegroundColor Red + } + } else { + Write-Host " Path does not exist" -ForegroundColor Yellow + } + } + + if (-not $dncConfig) { + Write-Host " No DNC configuration found in registry" -ForegroundColor Yellow + } + + return $dncConfig +} + +# Main function to collect all shopfloor configurations +function Get-ShopfloorConfigurations { + Write-Host "`nCollecting shopfloor-specific configurations..." -ForegroundColor Yellow + + $configurations = @{ + NetworkInterfaces = Get-NetworkInterfaceConfig + CommConfigs = Get-SerialPortConfig + DNCConfig = Get-DNCConfig + GERegistryInfo = Get-GERegistryInfo + } + + # Summary + Write-Host "`n Configuration Summary:" -ForegroundColor Green + Write-Host " Network Interfaces: $($configurations.NetworkInterfaces.Count)" -ForegroundColor Cyan + Write-Host " Comm Configs: $($configurations.CommConfigs.Count)" -ForegroundColor Cyan + Write-Host " DNC Config: $(if ($configurations.DNCConfig) { 'Yes' } else { 'No' })" -ForegroundColor Cyan + Write-Host " GE Registry (32-bit): $(if ($configurations.GERegistryInfo.Registry32Bit) { 'Yes' } else { 'No' })" -ForegroundColor Cyan + Write-Host " GE Registry (64-bit): $(if ($configurations.GERegistryInfo.Registry64Bit) { 'Yes' } else { 'No' })" -ForegroundColor Cyan + Write-Host " DualPath Enabled: $(if ($configurations.GERegistryInfo.DualPathEnabled -eq $null) { 'Not Found' } elseif ($configurations.GERegistryInfo.DualPathEnabled) { 'Yes' } else { 'No' })" -ForegroundColor Cyan + + return $configurations +} + +function Get-InstalledApplications { + <# + .SYNOPSIS + Detects UDC and CLM applications and their active status on shopfloor PCs + .DESCRIPTION + Checks for UDC (UDC.exe) and CLM (ppdcs.exe) processes and returns data + compatible with your existing installedapps table structure. + #> + + Write-Host " Scanning for UDC and CLM applications..." -ForegroundColor Yellow + + $detectedApps = @() + + try { + # Define the two applications we're looking for + $appsToCheck = @{ + 'UDC' = @{ + AppID = 2 # Universal Data Collector + ProcessName = 'UDC' # UDC.exe shows as 'UDC' in Get-Process + Description = 'Universal Data Collector' + } + 'CLM' = @{ + AppID = 4 # Legacy UDC (CLM) + ProcessName = 'ppdcs' # ppdcs.exe shows as 'ppdcs' in Get-Process + Description = 'Legacy UDC (Cell Level Manager)' + } + } + + foreach ($appName in $appsToCheck.Keys) { + $app = $appsToCheck[$appName] + + Write-Host " Checking for $appName (AppID: $($app.AppID))..." -ForegroundColor Gray + + # Check if the process is running + $isActive = $false + $processInfo = $null + + try { + $processes = Get-Process -Name $app.ProcessName -ErrorAction SilentlyContinue + if ($processes) { + $isActive = $true + $processInfo = $processes[0] # Take first instance if multiple + Write-Host " [ACTIVE] Found running process: $($app.ProcessName).exe (PID: $($processInfo.Id))" -ForegroundColor Green + } else { + Write-Host " [NOT ACTIVE] Process $($app.ProcessName).exe not running" -ForegroundColor Gray + } + } catch { + Write-Host " [ERROR] Failed to check process $($app.ProcessName): $($_.Exception.Message)" -ForegroundColor Yellow + } + + # Always return app info (both active and inactive) + $detectedApps += @{ + AppID = $app.AppID + AppName = $appName + Description = $app.Description + ProcessName = $app.ProcessName + IsActive = $isActive + ProcessID = if ($processInfo) { $processInfo.Id } else { $null } + ProcessStartTime = if ($processInfo) { $processInfo.StartTime } else { $null } + } + } + + # Business rule validation: Only one should be active + $activeApps = $detectedApps | Where-Object { $_.IsActive -eq $true } + + if ($activeApps.Count -gt 1) { + Write-Host " [WARNING] Multiple applications active simultaneously:" -ForegroundColor Red + foreach ($activeApp in $activeApps) { + Write-Host " - $($activeApp.AppName) (PID: $($activeApp.ProcessID))" -ForegroundColor Red + } + } elseif ($activeApps.Count -eq 1) { + $activeApp = $activeApps[0] + Write-Host " [OK] Single active application: $($activeApp.AppName) (PID: $($activeApp.ProcessID))" -ForegroundColor Green + } else { + Write-Host " [INFO] No UDC or CLM applications currently running" -ForegroundColor Gray + } + + return $detectedApps + + } catch { + Write-Host " [ERROR] Failed to scan for applications: $($_.Exception.Message)" -ForegroundColor Red + return @() + } +} \ No newline at end of file diff --git a/scripts/Invoke-RemoteAssetCollection.ps1 b/scripts/Invoke-RemoteAssetCollection.ps1 new file mode 100644 index 0000000..5735251 --- /dev/null +++ b/scripts/Invoke-RemoteAssetCollection.ps1 @@ -0,0 +1,484 @@ +#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 +} \ No newline at end of file diff --git a/scripts/Update-PC-CompleteAsset.ps1 b/scripts/Update-PC-CompleteAsset.ps1 new file mode 100644 index 0000000..f874d6e --- /dev/null +++ b/scripts/Update-PC-CompleteAsset.ps1 @@ -0,0 +1,1384 @@ +# Admin not required for data collection - WinRM setup will be skipped if not admin +<# +.SYNOPSIS + Complete PC asset collection and database update via hybrid proxy/dashboard architecture. + +.DESCRIPTION + This script performs comprehensive PC asset management: + 1. Collects system information (hostname, serial, manufacturer, user, etc.) + 2. Determines PC type (Engineer/Shopfloor/Standard) + 3. For shopfloor PCs: Collects machine assignment and network/comm configurations + 4. Gets Dell warranty information via proxy server (bypasses network restrictions) + 5. Stores all collected data in database via dashboard API + +.NOTES + Author: System Administrator + Date: 2025 + Version: 1.0 + Architecture: PowerShell -> Proxy (warranty) -> Dashboard API (storage) +#> + +param( + [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, # DEFAULT TO TRUE - Skip warranty lookups + + [Parameter(Mandatory=$false)] + [switch]$TestConnections = $false +) + +# ============================================================================= +# 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 + +# Import shopfloor configuration functions +. "$PSScriptRoot\Get-ShopfloorConfig.ps1" + +# Setup logging +$script:LogFile = "$PSScriptRoot\logs\Update-PC-CompleteAsset-$(Get-Date -Format 'yyyy-MM-dd').log" +$script:LogDir = "$PSScriptRoot\logs" + +function Write-Log { + param( + [string]$Message, + [string]$Level = "INFO", + [string]$ForegroundColor = "White" + ) + + $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + $logMessage = "[$timestamp] [$Level] $Message" + + # Write to console + Write-Host $Message -ForegroundColor $ForegroundColor + + # Write to log file + try { + if (-not (Test-Path $script:LogDir)) { + New-Item -ItemType Directory -Path $script:LogDir -Force | Out-Null + } + Add-Content -Path $script:LogFile -Value $logMessage -ErrorAction SilentlyContinue + } catch { + # Silently continue if logging fails + } +} + +function Reset-WinRMConfiguration { + <# + .SYNOPSIS + Resets and configures WinRM for HTTP remote management + .DESCRIPTION + Removes existing WinRM listeners and configures fresh HTTP listener on port 5985 + #> + + Write-Log " Resetting WinRM configuration..." -Level "INFO" -ForegroundColor Yellow + + try { + # First, check and fix network profile if set to Public + Write-Log " Checking network profile..." -Level "INFO" -ForegroundColor Gray + $profiles = Get-NetConnectionProfile + + foreach ($profile in $profiles) { + Write-Log " Interface '$($profile.Name)': $($profile.NetworkCategory)" -Level "INFO" -ForegroundColor Gray + } + + # Fix #1: Set machine network interfaces (192.168.x.x) to Private + # These shouldn't try to detect domain - they're for machine communication + Write-Log " Checking for machine network interfaces..." -Level "INFO" -ForegroundColor Gray + $adapters = Get-NetAdapter | Where-Object { $_.Status -eq 'Up' } + foreach ($adapter in $adapters) { + $ipConfig = Get-NetIPAddress -InterfaceIndex $adapter.ifIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue + if ($ipConfig.IPAddress -match '^192\.168\.') { + Write-Log " Found machine network: $($adapter.Name) ($($ipConfig.IPAddress))" -Level "INFO" -ForegroundColor Cyan + $profile = Get-NetConnectionProfile -InterfaceIndex $adapter.ifIndex -ErrorAction SilentlyContinue + if ($profile -and $profile.NetworkCategory -ne 'Private') { + Write-Log " Setting machine network '$($adapter.Name)' to Private..." -Level "INFO" -ForegroundColor Gray + Set-NetConnectionProfile -InterfaceIndex $adapter.ifIndex -NetworkCategory Private -ErrorAction SilentlyContinue + Write-Log " [OK] Machine network set to Private" -Level "SUCCESS" -ForegroundColor Green + } + } + } + + # Fix #5: Check and repair machine account trust relationship + Write-Log " Checking domain trust relationship..." -Level "INFO" -ForegroundColor Gray + $secureChannel = Test-ComputerSecureChannel -ErrorAction SilentlyContinue + if ($secureChannel) { + Write-Log " [OK] Domain trust relationship is healthy" -Level "SUCCESS" -ForegroundColor Green + } else { + Write-Log " [WARN] Domain trust may be broken, attempting repair..." -Level "WARN" -ForegroundColor Yellow + try { + # This requires domain admin creds or machine account to still be somewhat valid + $repairResult = Test-ComputerSecureChannel -Repair -ErrorAction SilentlyContinue + if ($repairResult) { + Write-Log " [OK] Domain trust repaired successfully" -Level "SUCCESS" -ForegroundColor Green + } else { + Write-Log " [WARN] Could not auto-repair trust - may need manual rejoin to domain" -Level "WARN" -ForegroundColor Yellow + } + } catch { + Write-Log " [WARN] Trust repair failed: $($_.Exception.Message)" -Level "WARN" -ForegroundColor Yellow + } + } + + # Now handle remaining Public profiles on corporate network + $publicProfiles = Get-NetConnectionProfile | Where-Object { $_.NetworkCategory -eq 'Public' } + + if ($publicProfiles) { + Write-Log " Found Public network profile(s), attempting to fix..." -Level "INFO" -ForegroundColor Yellow + + # Restart NLA service to re-detect domain + Write-Log " Restarting NLA service to detect domain..." -Level "INFO" -ForegroundColor Gray + Restart-Service NlaSvc -Force -ErrorAction SilentlyContinue + Start-Sleep -Seconds 5 + + # Re-check profiles after NLA restart + $publicProfiles = Get-NetConnectionProfile | Where-Object { $_.NetworkCategory -eq 'Public' } + + foreach ($profile in $publicProfiles) { + # Check if this is a corporate network (not 192.168.x.x) + $adapter = Get-NetAdapter -InterfaceIndex $profile.InterfaceIndex -ErrorAction SilentlyContinue + $ipConfig = Get-NetIPAddress -InterfaceIndex $profile.InterfaceIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue + + if ($ipConfig.IPAddress -notmatch '^192\.168\.') { + Write-Log " Corporate interface '$($profile.Name)' still Public after NLA restart" -Level "WARN" -ForegroundColor Yellow + } + + # Set to Private as fallback (can't manually set Domain) + Write-Log " Setting '$($profile.Name)' to Private..." -Level "INFO" -ForegroundColor Gray + Set-NetConnectionProfile -InterfaceIndex $profile.InterfaceIndex -NetworkCategory Private -ErrorAction SilentlyContinue + Write-Log " [OK] '$($profile.Name)' set to Private" -Level "SUCCESS" -ForegroundColor Green + } + } else { + Write-Log " [OK] All network profiles are Private/Domain" -Level "SUCCESS" -ForegroundColor Green + } + + # Stop WinRM service first + Write-Log " Stopping WinRM service..." -Level "INFO" -ForegroundColor Gray + Stop-Service WinRM -Force -ErrorAction SilentlyContinue + Start-Sleep -Seconds 2 + Write-Log " WinRM service stopped" -Level "INFO" -ForegroundColor Gray + + # Delete all existing listeners + Write-Log " Removing existing WinRM listeners..." -Level "INFO" -ForegroundColor Gray + Remove-Item -Path WSMan:\localhost\Listener\* -Recurse -Force -ErrorAction SilentlyContinue + Write-Log " Existing listeners removed" -Level "INFO" -ForegroundColor Gray + + # Start WinRM service + Write-Log " Starting WinRM service..." -Level "INFO" -ForegroundColor Gray + Start-Service WinRM -ErrorAction Stop + Set-Service WinRM -StartupType Automatic + Write-Log " WinRM service started and set to Automatic" -Level "INFO" -ForegroundColor Gray + + # Run quick config to set defaults + Write-Log " Running WinRM quickconfig..." -Level "INFO" -ForegroundColor Gray + $quickConfigResult = winrm quickconfig -quiet 2>&1 + Write-Log " WinRM quickconfig completed" -Level "INFO" -ForegroundColor Gray + + # Create HTTP listener on all addresses + Write-Log " Creating HTTP listener on port 5985..." -Level "INFO" -ForegroundColor Gray + $existingHttp = Get-ChildItem WSMan:\localhost\Listener -ErrorAction SilentlyContinue | + Where-Object { $_.Keys -contains "Transport=HTTP" } + + if (-not $existingHttp) { + New-Item -Path WSMan:\localhost\Listener -Transport HTTP -Address * -Force | Out-Null + Write-Log " HTTP listener created" -Level "INFO" -ForegroundColor Gray + } else { + Write-Log " HTTP listener already exists" -Level "INFO" -ForegroundColor Gray + } + + # Configure WinRM settings + Write-Log " Configuring WinRM authentication settings..." -Level "INFO" -ForegroundColor Gray + Set-Item WSMan:\localhost\Service\Auth\Basic -Value $false + Set-Item WSMan:\localhost\Service\Auth\Negotiate -Value $true + Set-Item WSMan:\localhost\Service\Auth\Kerberos -Value $true + Set-Item WSMan:\localhost\Service\Auth\CredSSP -Value $false + Set-Item WSMan:\localhost\Service\AllowUnencrypted -Value $false + Write-Log " Auth: Basic=false, Negotiate=true, Kerberos=true, CredSSP=false" -Level "INFO" -ForegroundColor Gray + + # Set MaxMemoryPerShellMB for better performance + Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB -Value 1024 -ErrorAction SilentlyContinue + Write-Log " MaxMemoryPerShellMB set to 1024" -Level "INFO" -ForegroundColor Gray + + # Enable LocalAccountTokenFilterPolicy for remote admin access + Write-Log " Enabling LocalAccountTokenFilterPolicy..." -Level "INFO" -ForegroundColor Gray + $regPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" + Set-ItemProperty -Path $regPath -Name "LocalAccountTokenFilterPolicy" -Value 1 -Type DWord -Force -ErrorAction SilentlyContinue + Write-Log " LocalAccountTokenFilterPolicy enabled" -Level "INFO" -ForegroundColor Gray + + # Grant WinRM access to Remote Management Users and Administrators + Write-Log " Configuring WinRM security descriptor..." -Level "INFO" -ForegroundColor Gray + try { + # Get current SDDL + $sddl = (Get-Item WSMan:\localhost\Service\RootSDDL).Value + Write-Log " Current SDDL: $sddl" -Level "INFO" -ForegroundColor Gray + + # Enable PSRemoting to set proper permissions + Enable-PSRemoting -Force -SkipNetworkProfileCheck 2>&1 | Out-Null + Write-Log " PSRemoting enabled" -Level "INFO" -ForegroundColor Gray + } catch { + Write-Log " Note: Could not update SDDL directly, using Enable-PSRemoting" -Level "INFO" -ForegroundColor Gray + } + + # Restart WinRM to apply all changes + Write-Log " Restarting WinRM service to apply changes..." -Level "INFO" -ForegroundColor Gray + Restart-Service WinRM -Force + Start-Sleep -Seconds 2 + Write-Log " WinRM service restarted" -Level "INFO" -ForegroundColor Gray + + # Configure firewall rule + Write-Log " Configuring firewall rule..." -Level "INFO" -ForegroundColor Gray + $ruleName = "Windows Remote Management (HTTP-In)" + $existingRule = Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue + + if (-not $existingRule) { + New-NetFirewallRule -DisplayName $ruleName ` + -Name "WINRM-HTTP-In-TCP" ` + -Profile Domain,Private ` + -LocalPort 5985 ` + -Protocol TCP ` + -Direction Inbound ` + -Action Allow ` + -Enabled True | Out-Null + Write-Log " Firewall rule '$ruleName' created" -Level "INFO" -ForegroundColor Gray + } else { + Enable-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue + Write-Log " Firewall rule '$ruleName' enabled" -Level "INFO" -ForegroundColor Gray + } + + # Verify listener is working + Write-Log " Verifying WinRM listener..." -Level "INFO" -ForegroundColor Gray + $listeners = winrm enumerate winrm/config/listener 2>&1 + + if ($listeners -match "Transport = HTTP" -and $listeners -match "Port = 5985") { + Write-Log " [OK] WinRM HTTP listener configured on port 5985" -Level "SUCCESS" -ForegroundColor Green + + # Show listener details + $portCheck = netstat -an | Select-String ":5985.*LISTENING" + if ($portCheck) { + Write-Log " [OK] Port 5985 is listening" -Level "SUCCESS" -ForegroundColor Green + } + + return $true + } else { + Write-Log " [WARN] WinRM listener may not be configured correctly" -Level "WARN" -ForegroundColor Yellow + return $false + } + } + catch { + Write-Log " [FAIL] Error configuring WinRM: $($_.Exception.Message)" -Level "ERROR" -ForegroundColor Red + return $false + } +} + +function Add-WinRMAdminGroup { + <# + .SYNOPSIS + Adds the WinRM management security group to local Administrators and Remote Management Users + .DESCRIPTION + Adds logon\g03078610 to both local Administrators and Remote Management Users groups + to enable WinRM remote management + #> + param( + [string]$GroupToAdd = "logon\g03078610" + ) + + Write-Log " Configuring WinRM access groups..." -Level "INFO" -ForegroundColor Yellow + Write-Log " Target group: $GroupToAdd" -Level "INFO" -ForegroundColor Gray + + $overallSuccess = $true + + # Add to Administrators group + try { + Write-Log " Checking local Administrators group..." -Level "INFO" -ForegroundColor Gray + $adminGroup = [ADSI]"WinNT://./Administrators,group" + $members = @($adminGroup.Invoke("Members")) | ForEach-Object { + $_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null) + } + + Write-Log " Current Administrators members: $($members -join ', ')" -Level "INFO" -ForegroundColor Gray + + $groupName = $GroupToAdd.Split('\')[-1] + + if ($members -contains $groupName) { + Write-Log " [OK] $GroupToAdd is already in Administrators" -Level "SUCCESS" -ForegroundColor Green + } else { + Write-Log " Adding $GroupToAdd to Administrators..." -Level "INFO" -ForegroundColor Yellow + $result = net localgroup Administrators $GroupToAdd /add 2>&1 + + if ($LASTEXITCODE -eq 0 -or $result -match "already a member") { + Write-Log " [OK] Added $GroupToAdd to Administrators" -Level "SUCCESS" -ForegroundColor Green + } else { + Write-Log " [FAIL] Failed to add to Administrators: $result" -Level "ERROR" -ForegroundColor Red + $overallSuccess = $false + } + } + } + catch { + Write-Log " [FAIL] Error with Administrators group: $($_.Exception.Message)" -Level "ERROR" -ForegroundColor Red + $overallSuccess = $false + } + + # Add to Remote Management Users group + try { + Write-Log " Checking Remote Management Users group..." -Level "INFO" -ForegroundColor Gray + $rmGroup = [ADSI]"WinNT://./Remote Management Users,group" + $rmMembers = @($rmGroup.Invoke("Members")) | ForEach-Object { + $_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null) + } + + Write-Log " Current Remote Management Users members: $($rmMembers -join ', ')" -Level "INFO" -ForegroundColor Gray + + $groupName = $GroupToAdd.Split('\')[-1] + + if ($rmMembers -contains $groupName) { + Write-Log " [OK] $GroupToAdd is already in Remote Management Users" -Level "SUCCESS" -ForegroundColor Green + } else { + Write-Log " Adding $GroupToAdd to Remote Management Users..." -Level "INFO" -ForegroundColor Yellow + $result = net localgroup "Remote Management Users" $GroupToAdd /add 2>&1 + + if ($LASTEXITCODE -eq 0 -or $result -match "already a member") { + Write-Log " [OK] Added $GroupToAdd to Remote Management Users" -Level "SUCCESS" -ForegroundColor Green + } else { + Write-Log " [FAIL] Failed to add to Remote Management Users: $result" -Level "ERROR" -ForegroundColor Red + $overallSuccess = $false + } + } + } + catch { + Write-Log " [FAIL] Error with Remote Management Users group: $($_.Exception.Message)" -Level "ERROR" -ForegroundColor Red + $overallSuccess = $false + } + + return $overallSuccess +} + +function Get-DashboardURL { + param([string]$ProvidedURL) + + if (-not [string]::IsNullOrEmpty($ProvidedURL)) { + Write-Host " Using provided URL: $ProvidedURL" -ForegroundColor Gray + return $ProvidedURL + } + + # Check environment variable + $envURL = [Environment]::GetEnvironmentVariable("ASSET_DASHBOARD_URL", "User") + if (-not [string]::IsNullOrEmpty($envURL)) { + Write-Host " Using environment variable URL: $envURL" -ForegroundColor Gray + return $envURL + } + + # Check for config file + $configPath = "$PSScriptRoot\dashboard-config.json" + if (Test-Path $configPath) { + try { + $config = Get-Content $configPath | ConvertFrom-Json + if ($config.DashboardURL) { + return $config.DashboardURL + } + } + catch { + Write-Verbose "Could not read dashboard config file: $_" + } + } + + # Auto-discovery with verbose logging + Write-Host " Starting dashboard URL auto-discovery..." -ForegroundColor Yellow + $candidates = @( + "https://tsgwp00525.rd.ds.ge.com/shopdb/api.asp", + "http://192.168.122.151:8080/api.asp", + "http://localhost:8080/api.asp", + "http://10.48.130.197/dashboard-v2/api.php", + "http://localhost/test/dashboard/api.php", + "http://127.0.0.1/test/dashboard/api.php", + "http://10.48.130.197/api.php", + "http://localhost/api.php", + "http://127.0.0.1/api.php" + ) + + foreach ($url in $candidates) { + try { + Write-Host " Testing: $url" -ForegroundColor Gray + $testResponse = Invoke-RestMethod -Uri "$url?action=getDashboardData" -Method Get -TimeoutSec 5 -ErrorAction Stop + if ($testResponse.success) { + Write-Host " [SUCCESS] Dashboard found at: $url" -ForegroundColor Green + return $url + } else { + Write-Host " [FAIL] Dashboard responded but not successful" -ForegroundColor Yellow + } + } + catch { + Write-Host " [FAIL] Cannot reach: $($_.Exception.Message)" -ForegroundColor Red + } + } + + # If auto-discovery fails, default to the expected URL + $defaultUrl = "https://tsgwp00525.rd.ds.ge.com/shopdb/api.asp" + Write-Host " [FALLBACK] Using default URL: $defaultUrl" -ForegroundColor Yellow + return $defaultUrl +} + +function Test-ProxyConnection { + param([string]$ProxyURL) + + try { + Write-Host "Testing proxy connection..." -ForegroundColor Yellow + + $uri = "$ProxyURL" + "?vendor=dell&action=test-config" + $response = Invoke-RestMethod -Uri $uri -Method Get -TimeoutSec 15 + + if ($response.success) { + Write-Host "[OK] Proxy server accessible and Dell API configured" -ForegroundColor Green + return $true + } else { + Write-Host "[FAIL] Proxy issue: $($response.error)" -ForegroundColor Red + return $false + } + } + catch { + Write-Host "[FAIL] Cannot reach proxy: $($_.Exception.Message)" -ForegroundColor Red + return $false + } +} + +function Test-DashboardConnection { + param([string]$DashboardURL) + + try { + Write-Host "Testing dashboard connection..." -ForegroundColor Yellow + + $response = Invoke-RestMethod -Uri "$DashboardURL?action=getDashboardData" -Method Get -TimeoutSec 10 + if ($response.success) { + Write-Host "[OK] Dashboard API accessible" -ForegroundColor Green + return $true + } else { + Write-Host "[FAIL] Dashboard not responding properly" -ForegroundColor Red + return $false + } + } + catch { + Write-Host "[FAIL] Cannot reach dashboard: $($_.Exception.Message)" -ForegroundColor Red + return $false + } +} + +function Test-AppsFolder { + return Test-Path "C:\Apps" +} + +function Test-VDriveAccess { + try { + $vDrive = Get-PSDrive -Name V -ErrorAction SilentlyContinue + if ($vDrive) { + $null = Get-ChildItem V:\ -ErrorAction Stop | Select-Object -First 1 + return $true + } + return $false + } + catch { + return $false + } +} + +function Test-WindowsLTSC { + try { + $os = Get-CimInstance -ClassName CIM_OperatingSystem + $osCaption = $os.Caption + + if ($osCaption -match "LTSC|LTSB") { + return $true + } + + $productName = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name ProductName -ErrorAction SilentlyContinue).ProductName + if ($productName -match "LTSC|LTSB") { + return $true + } + + return $false + } + catch { + return $false + } +} + +function Get-TrackedApplications { + try { + $csvPath = "$PSScriptRoot\applications.csv" + if (-not (Test-Path $csvPath)) { + Write-Warning "Applications CSV not found at $csvPath - using fallback patterns" + return @( + @{ app_name = "UDC"; search_patterns = "Universal Data Collection"; enabled = $true } + @{ app_name = "PPDCS"; search_patterns = "PPDCS"; enabled = $true } + @{ app_name = "Oracle"; search_patterns = "Oracle.*Database"; enabled = $true } + @{ app_name = "Tanium"; search_patterns = "Tanium"; enabled = $true } + @{ app_name = "eDNC"; search_patterns = "eDNC"; enabled = $true } + ) + } + + $trackedApps = @() + $csvContent = Import-Csv -Path $csvPath + + foreach ($row in $csvContent) { + if ($row.enabled -eq "1") { + $trackedApps += @{ + app_name = $row.app_name + app_id = $row.app_id + search_patterns = $row.search_patterns + description = $row.description + } + } + } + + Write-Host " Loaded $($trackedApps.Count) enabled applications from CSV" -ForegroundColor Cyan + return $trackedApps + } + catch { + Write-Warning "Failed to read applications CSV: $($_.Exception.Message) - using fallback patterns" + return @( + @{ app_name = "UDC"; app_id = "2"; search_patterns = "Universal Data Collection"; enabled = $true } + @{ app_name = "PPDCS"; app_id = "4"; search_patterns = "PPDCS"; enabled = $true } + @{ app_name = "Oracle"; app_id = "7"; search_patterns = "Oracle.*Database"; enabled = $true } + @{ app_name = "Tanium"; app_id = "30"; search_patterns = "Tanium"; enabled = $true } + @{ app_name = "eDNC"; app_id = ""; search_patterns = "eDNC"; enabled = $true } + ) + } +} + +function Get-GEMachineNumber { + param([string]$Hostname) + + # Check GE Aircraft Engines registry paths for MachineNo + $gePaths = @( + "HKLM:\Software\GE Aircraft Engines\DNC\General", + "HKLM:\Software\WOW6432Node\GE Aircraft Engines\DNC\General" + ) + + foreach ($gePath in $gePaths) { + if (Test-Path $gePath) { + try { + $machineNo = Get-ItemProperty -Path $gePath -Name "MachineNo" -ErrorAction Stop + if ($machineNo.MachineNo) { + return $machineNo.MachineNo + } + } + catch { + # Continue to next path + } + } + } + + # Check if hostname indicates a specific GE machine + if ($Hostname -match '[HG](\d{3})') { + $machineNum = $Matches[1] + return "M$machineNum" + } + + return $null +} + +function Get-PCType { + param( + [bool]$HasAppsFolder, + [bool]$HasVDriveAccess + ) + + # Check if on logon.ds.ge.com domain (Shopfloor PCs) + try { + $domain = (Get-WmiObject Win32_ComputerSystem).Domain + Write-Host " Domain detected: $domain" -ForegroundColor Gray + if ($domain -eq "logon.ds.ge.com") { + Write-Host " [OK] Shopfloor domain detected" -ForegroundColor Green + + # Check for specific machine type applications + $installedApps = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*", + "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" -ErrorAction SilentlyContinue | + Where-Object { $_.DisplayName } | + Select-Object -ExpandProperty DisplayName + + # ================================================================ + # PC Type Detection based on installed software + # Priority: CMM > Wax Trace > Keyence > EAS1000 > Genspect > Heat Treat > Shopfloor + # ================================================================ + + # CMM Detection: PC-DMIS, goCMM, DODA + $hasPcDmis = $installedApps -match "PC-DMIS|PCDMIS" + $hasGoCMM = $installedApps -match "^goCMM" + $hasDODA = $installedApps -match "Dovetail Digital Analysis|DODA" + + # Also check common PC-DMIS installation paths + if (-not $hasPcDmis) { + $pcDmisPaths = @( + "C:\Program Files\Hexagon\PC-DMIS*", + "C:\Program Files (x86)\Hexagon\PC-DMIS*", + "C:\Program Files\WAI\PC-DMIS*", + "C:\Program Files (x86)\WAI\PC-DMIS*", + "C:\ProgramData\Hexagon\PC-DMIS*" + ) + foreach ($dmisPath in $pcDmisPaths) { + if (Test-Path $dmisPath) { $hasPcDmis = $true; break } + } + } + + if ($hasPcDmis -or $hasGoCMM -or $hasDODA) { + $detected = @() + if ($hasPcDmis) { $detected += "PC-DMIS" } + if ($hasGoCMM) { $detected += "goCMM" } + if ($hasDODA) { $detected += "DODA" } + Write-Host " [OK] CMM software detected ($($detected -join ', ')) - CMM PC" -ForegroundColor Cyan + return "CMM" + } + + # Wax Trace Detection: FormTracePak, FormStatusMonitor + $hasFormTracePak = $installedApps -match "FormTracePak|Formtracepak|Form Trace|FormTrace" + $hasFormStatusMonitor = $installedApps -match "FormStatusMonitor" + + # Check file path fallback for FormTracePak + if (-not $hasFormTracePak) { + $ftPaths = @("C:\Program Files\MitutoyoApp*", "C:\Program Files (x86)\MitutoyoApp*") + foreach ($ftPath in $ftPaths) { + if (Test-Path $ftPath) { $hasFormTracePak = $true; break } + } + } + + if ($hasFormTracePak -or $hasFormStatusMonitor) { + $detected = @() + if ($hasFormTracePak) { $detected += "FormTracePak" } + if ($hasFormStatusMonitor) { $detected += "FormStatusMonitor" } + Write-Host " [OK] Wax Trace software detected ($($detected -join ', ')) - Wax Trace PC" -ForegroundColor Cyan + return "Wax Trace" + } + + # Keyence Detection: VR Series, Keyence VR USB Driver + $hasKeyence = $installedApps -match "VR-3000|VR-5000|VR-6000|KEYENCE VR" + + if ($hasKeyence) { + Write-Host " [OK] Keyence VR Series detected - Keyence PC" -ForegroundColor Cyan + return "Keyence" + } + + # EAS1000 Detection: GageCal, NI Software (National Instruments) + $hasGageCal = $installedApps -match "^GageCal" + $hasNISoftware = $installedApps -match "^NI-|National Instruments|NI System|NI Measurement|NI LabVIEW" + + if ($hasGageCal -or $hasNISoftware) { + $detected = @() + if ($hasGageCal) { $detected += "GageCal" } + if ($hasNISoftware) { $detected += "NI Software" } + Write-Host " [OK] EAS1000 software detected ($($detected -join ', ')) - EAS1000 PC" -ForegroundColor Cyan + return "EAS1000" + } + + # Genspect detection (could be Keyence or EAS1000 - default to Measuring) + $hasGenspect = $installedApps -match "^Genspect" + if ($hasGenspect) { + Write-Host " [OK] Genspect detected - Measuring Tool PC" -ForegroundColor Cyan + return "Genspect" + } + + # Heat Treat Detection: HeatTreat application + $hasHeatTreat = $installedApps -match "^HeatTreat" + if ($hasHeatTreat) { + Write-Host " [OK] HeatTreat software detected - Heat Treat PC" -ForegroundColor Cyan + return "Heat Treat" + } + + return "Shopfloor" + } + } catch { + Write-Host " [WARN] Could not detect domain: $($_.Exception.Message)" -ForegroundColor Yellow + } + + if ($HasAppsFolder -and $HasVDriveAccess) { + return "Engineer" + } + else { + return "Standard" + } +} + +function Collect-SystemInfo { + Write-Host "Collecting comprehensive system information..." -ForegroundColor Green + + $systemInfo = @{} + + # Basic system info + $systemInfo.Hostname = $env:COMPUTERNAME + + try { + $computerSystem = Get-CimInstance -Class CIM_ComputerSystem -ErrorAction Stop + $bios = Get-CimInstance -Class CIM_BIOSElement -ErrorAction Stop + $os = Get-CimInstance -Class CIM_OperatingSystem -ErrorAction Stop + + $systemInfo.Manufacturer = $computerSystem.Manufacturer + $systemInfo.Model = $computerSystem.Model + $systemInfo.SerialNumber = $bios.SerialNumber + $systemInfo.ServiceTag = $bios.SerialNumber # Often same as serial for Dell + $systemInfo.TotalPhysicalMemory = [Math]::Round($computerSystem.TotalPhysicalMemory / 1GB, 2) + $systemInfo.DomainRole = $computerSystem.DomainRole + + # OS Information + $systemInfo.OSVersion = $os.Caption + $systemInfo.LastBootUpTime = $os.LastBootUpTime + $systemInfo.CurrentTimeZone = (Get-TimeZone).Id + + # Logged-in user + if ($computerSystem.UserName) { + $systemInfo.LoggedInUser = $computerSystem.UserName.Split('\')[-1] + } else { + $systemInfo.LoggedInUser = "No user logged in" + } + + } + catch { + Write-Warning "Failed to retrieve WMI information: $_" + $systemInfo.Manufacturer = "Unknown" + $systemInfo.Model = "Unknown" + $systemInfo.SerialNumber = "Unknown" + $systemInfo.ServiceTag = "Unknown" + $systemInfo.LoggedInUser = "Unknown" + $systemInfo.OSVersion = "Unknown" + } + + # PC Type determination + $hasApps = Test-AppsFolder + $hasVDrive = Test-VDriveAccess + + $systemInfo.PCType = Get-PCType -HasAppsFolder $hasApps -HasVDriveAccess $hasVDrive + + # Add application detection for shopfloor PCs + if ($systemInfo.PCType -eq "Shopfloor") { + Write-Host " Detecting shopfloor applications (UDC/CLM)..." -ForegroundColor Yellow + $systemInfo.InstalledApplications = Get-InstalledApplications + } else { + Write-Host " Skipping application detection (PC Type: $($systemInfo.PCType))" -ForegroundColor Gray + $systemInfo.InstalledApplications = @() + } + + # GE Machine Number + $systemInfo.MachineNo = Get-GEMachineNumber -Hostname $systemInfo.Hostname + + # Collect installed applications for logging and database tracking + Write-Host " Collecting installed applications..." -ForegroundColor Yellow + try { + $installedApps = @() + $installedApps += Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-Object {$_.DisplayName} + $installedApps += Get-ItemProperty HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-Object {$_.DisplayName} + + $filteredApps = $installedApps | Select-Object DisplayName, DisplayVersion | Sort-Object DisplayName -Unique + + Write-Host " Found $($filteredApps.Count) installed applications:" -ForegroundColor Gray + foreach ($app in $filteredApps) { + $version = if ($app.DisplayVersion) { " (v$($app.DisplayVersion))" } else { "" } + Write-Host " - $($app.DisplayName)$version" -ForegroundColor Gray + } + + # Prepare application data for database tracking (specific apps only) + $trackedApps = @() + $csvTrackedApps = Get-TrackedApplications + + # Track which appids we've already added to avoid duplicates + $seenAppIds = @{} + + foreach ($app in $filteredApps) { + $appName = $app.DisplayName + $appVersion = $app.DisplayVersion + + # Check against CSV-defined applications + foreach ($csvApp in $csvTrackedApps) { + # Skip if no app_id defined in CSV + if (-not $csvApp.app_id -or $csvApp.app_id -eq "") { continue } + + if ($appName -match $csvApp.search_patterns) { + $appId = [int]$csvApp.app_id + + # Skip if we've already tracked this appid + if ($seenAppIds.ContainsKey($appId)) { + Write-Host " Skipping duplicate: $($csvApp.app_name) (ID:$appId) = $appName" -ForegroundColor DarkGray + break + } + + $seenAppIds[$appId] = $true + $trackedApps += @{ + appid = $appId # Database appid + appname = $csvApp.app_name # Short name from CSV (e.g., "Tanium") + version = $appVersion # Detected version + displayname = $appName # Full registry name for reference + } + Write-Host " Matched: $($csvApp.app_name) (ID:$appId) = $appName v$appVersion" -ForegroundColor Cyan + break # Found a match, no need to check other patterns + } + } + } + + $systemInfo.TrackedApplications = $trackedApps + Write-Host " Found $($trackedApps.Count) tracked applications for database" -ForegroundColor Cyan + } + catch { + Write-Host " [WARN] Failed to collect installed applications: $($_.Exception.Message)" -ForegroundColor Yellow + $systemInfo.TrackedApplications = @() + } + + # Collect running processes (for log analysis) + Write-Host " Running processes:" -ForegroundColor Yellow + try { + $processes = Get-Process | Select-Object -ExpandProperty Name | Sort-Object -Unique + Write-Host " $($processes -join ', ')" -ForegroundColor Gray + } + catch { + Write-Host " [WARN] Failed to collect running processes: $($_.Exception.Message)" -ForegroundColor Yellow + } + + # Display collected info + Write-Host " System Details:" -ForegroundColor Cyan + Write-Host " Hostname: $($systemInfo.Hostname)" + Write-Host " Manufacturer: $($systemInfo.Manufacturer)" + Write-Host " Model: $($systemInfo.Model)" + Write-Host " Serial: $($systemInfo.SerialNumber)" + Write-Host " PC Type: $($systemInfo.PCType)" + Write-Host " User: $($systemInfo.LoggedInUser)" + if ($systemInfo.MachineNo) { + Write-Host " Machine No: $($systemInfo.MachineNo)" + } + Write-Host " Memory: $($systemInfo.TotalPhysicalMemory) GB" + Write-Host " OS: $($systemInfo.OSVersion)" + + return $systemInfo +} + +function Collect-ShopfloorInfo { + param([hashtable]$SystemInfo) + + if ($SystemInfo.PCType -ne "Shopfloor") { + return $null + } + + Write-Host "" + Write-Host "Collecting shopfloor-specific configurations..." -ForegroundColor Yellow + + try { + $shopfloorConfigs = Get-ShopfloorConfigurations + + Write-Host " Shopfloor Configuration Summary:" -ForegroundColor Cyan + Write-Host " Network Interfaces: $($shopfloorConfigs.NetworkInterfaces.Count)" + Write-Host " Communication Configs: $($shopfloorConfigs.CommConfigs.Count)" + Write-Host " DNC Config: $(if ($shopfloorConfigs.DNCConfig) { 'Yes' } else { 'No' })" + + return $shopfloorConfigs + } + catch { + Write-Warning "Failed to collect shopfloor configurations: $_" + return $null + } +} + +function Get-DefaultPrinterFQDN { + try { + Write-Host " Collecting default printer information..." -ForegroundColor Yellow + $defaultPrinter = Get-WmiObject -Query "SELECT * FROM Win32_Printer WHERE Default=$true" -ErrorAction Stop + + if ($defaultPrinter -and $defaultPrinter.PortName) { + $portName = $defaultPrinter.PortName + Write-Host " Default Printer: $($defaultPrinter.Name)" -ForegroundColor Gray + Write-Host " Port Name: $portName" -ForegroundColor Gray + + # Check if this is a network printer (not a local/virtual printer) + # Network printer ports are: IP addresses, FQDNs, or UNC paths + # Skip local printer ports: USB*, LPT*, COM*, PORTPROMPT:, FILE:, NUL:, etc. + $localPorts = @('USB', 'LPT', 'COM', 'PORTPROMPT:', 'FILE:', 'NUL:', 'XPS', 'PDF') + $isLocalPrinter = $false + + foreach ($localPort in $localPorts) { + if ($portName -like "$localPort*") { + $isLocalPrinter = $true + break + } + } + + if ($isLocalPrinter) { + Write-Host " [SKIP] Local/virtual printer detected (port: $portName) - not sending to database" -ForegroundColor Yellow + return $null + } + + # This appears to be a network printer (IP, FQDN, or UNC path) + Write-Host " [OK] Network printer detected - will send to database" -ForegroundColor Green + return $portName + } else { + Write-Host " No default printer found or no port available" -ForegroundColor Yellow + return $null + } + } + catch { + Write-Warning "Failed to get default printer information: $_" + return $null + } +} + +function Send-PrinterMappingToDashboard { + param( + [string]$Hostname, + [string]$PrinterFQDN, + [string]$DashboardURL + ) + + if ([string]::IsNullOrEmpty($PrinterFQDN)) { + Write-Host " No printer FQDN to send - skipping printer mapping" -ForegroundColor Gray + return $true + } + + try { + Write-Host " Sending printer mapping to dashboard..." -ForegroundColor Yellow + Write-Host " Hostname: $Hostname" -ForegroundColor Gray + Write-Host " Printer FQDN: $PrinterFQDN" -ForegroundColor Gray + + $postData = @{ + action = 'updatePrinterMapping' + hostname = $Hostname + printerFQDN = $PrinterFQDN + } + + $headers = @{ + 'Content-Type' = 'application/x-www-form-urlencoded' + } + + $response = Invoke-RestMethod -Uri $DashboardURL -Method Post -Body $postData -Headers $headers -TimeoutSec 30 + + # Debug: Show raw response structure + Write-Host " DEBUG Response: $($response | ConvertTo-Json -Compress)" -ForegroundColor Magenta + + if ($response.success) { + Write-Host " [OK] Printer mapping updated successfully!" -ForegroundColor Green + Write-Host " Printer ID: $($response.data.printerId)" -ForegroundColor Gray + Write-Host " Machines Updated: $($response.data.machinesUpdated)" -ForegroundColor Gray + Write-Host " Match Method: $($response.data.matchMethod)" -ForegroundColor Gray + return $true + } else { + Write-Host " [WARN] Printer mapping failed: $($response.message)" -ForegroundColor Yellow + Write-Host " DEBUG Error Response: $($response | ConvertTo-Json)" -ForegroundColor Red + return $false + } + } + catch { + Write-Host " [WARN] Error sending printer mapping: $($_.Exception.Message)" -ForegroundColor Yellow + return $false + } +} + +function Send-InstalledAppsToDashboard { + param( + [string]$Hostname, + [array]$TrackedApps, + [string]$DashboardURL + ) + + if (!$TrackedApps -or $TrackedApps.Count -eq 0) { + Write-Host " No tracked applications to send - skipping app mapping" -ForegroundColor Gray + return $true + } + + try { + Write-Host " Sending tracked applications to dashboard..." -ForegroundColor Yellow + Write-Host " Hostname: $Hostname" -ForegroundColor Gray + Write-Host " Tracked Apps: $($TrackedApps.Count)" -ForegroundColor Gray + + # Debug: Show each app with version + foreach ($app in $TrackedApps) { + Write-Host " -> appid=$($app.appid), appname='$($app.appname)', version='$($app.version)'" -ForegroundColor Magenta + } + + $appsJson = $TrackedApps | ConvertTo-Json -Compress + Write-Host " DEBUG JSON: $appsJson" -ForegroundColor Magenta + + $postData = @{ + action = 'updateInstalledApps' + hostname = $Hostname + installedApps = $appsJson + } + + $headers = @{ + 'Content-Type' = 'application/x-www-form-urlencoded' + } + + $response = Invoke-RestMethod -Uri $DashboardURL -Method Post -Body $postData -Headers $headers -TimeoutSec 30 + + if ($response.success) { + Write-Host " [OK] Installed applications updated successfully!" -ForegroundColor Green + Write-Host " Apps Processed: $($response.appsProcessed)" -ForegroundColor Gray + Write-Host " Machine ID: $($response.machineid)" -ForegroundColor Gray + if ($response.debugError) { + Write-Host " DEBUG ERROR: $($response.debugError)" -ForegroundColor Red + } + return $true + } else { + Write-Host " [WARN] Application mapping failed: $($response.message)" -ForegroundColor Yellow + return $false + } + } + catch { + Write-Host " [WARN] Error sending application mapping: $($_.Exception.Message)" -ForegroundColor Yellow + return $false + } +} + +function Get-WarrantyFromProxy { + param( + [string]$ServiceTag, + [string]$ProxyURL + ) + + if ([string]::IsNullOrEmpty($ServiceTag) -or $ServiceTag -eq "Unknown") { + Write-Host " No valid service tag - skipping warranty lookup" -ForegroundColor Gray + return $null + } + + try { + Write-Host "Getting warranty data from proxy server..." -ForegroundColor Yellow + + $uri = "$ProxyURL" + "?vendor=dell&action=warranty&servicetag=$ServiceTag" + $response = Invoke-RestMethod -Uri $uri -Method Get -TimeoutSec 30 + + if ($response.success -and $response.warranties -and $response.warranties.Count -gt 0) { + $warranty = $response.warranties[0] + + Write-Host " [OK] Warranty data retrieved:" -ForegroundColor Green + Write-Host " Status: $($warranty.warrantyStatus)" + Write-Host " End Date: $($warranty.warrantyEndDate)" + Write-Host " Days Remaining: $($warranty.daysRemaining)" + Write-Host " Service Level: $($warranty.serviceLevel)" + + return $warranty + } else { + Write-Host " [WARN] No warranty data found for service tag: $ServiceTag" -ForegroundColor Yellow + return $null + } + } + catch { + Write-Host " [WARN] Error getting warranty from proxy: $($_.Exception.Message)" -ForegroundColor Yellow + return $null + } +} + +function Send-CompleteDataToDashboard { + param( + [hashtable]$SystemInfo, + [object]$ShopfloorInfo, + [object]$WarrantyData, + [string]$DashboardURL + ) + + try { + Write-Host "Sending complete asset data to dashboard..." -ForegroundColor Yellow + Write-Host " Dashboard URL: $DashboardURL" -ForegroundColor Gray + + # Prepare comprehensive data package + $postData = @{ + action = 'updateCompleteAsset' + + # Basic system information + hostname = $SystemInfo.Hostname + serialNumber = $SystemInfo.SerialNumber + serviceTag = $SystemInfo.ServiceTag + manufacturer = $SystemInfo.Manufacturer + model = $SystemInfo.Model + pcType = $SystemInfo.PCType + loggedInUser = $SystemInfo.LoggedInUser + machineNo = $SystemInfo.MachineNo + osVersion = $SystemInfo.OSVersion + totalPhysicalMemory = $SystemInfo.TotalPhysicalMemory + domainRole = $SystemInfo.DomainRole + currentTimeZone = $SystemInfo.CurrentTimeZone + lastBootUpTime = $SystemInfo.LastBootUpTime + } + + # Add warranty data if available + if ($WarrantyData) { + $postData.warrantyEndDate = $WarrantyData.warrantyEndDate + $postData.warrantyStatus = $WarrantyData.warrantyStatus + $postData.warrantyServiceLevel = $WarrantyData.serviceLevel + $postData.warrantyDaysRemaining = $WarrantyData.daysRemaining + } + + # Add shopfloor data if available + if ($ShopfloorInfo) { + Write-Host " ShopfloorInfo object contains DNCConfig: $(if ($ShopfloorInfo.DNCConfig) { 'YES' } else { 'NO' })" -ForegroundColor Gray + Write-Host " ShopfloorInfo object contains GERegistryInfo: $(if ($ShopfloorInfo.GERegistryInfo) { 'YES' } else { 'NO' })" -ForegroundColor Gray + + $postData.networkInterfaces = $ShopfloorInfo.NetworkInterfaces | ConvertTo-Json -Compress + $postData.commConfigs = $ShopfloorInfo.CommConfigs | ConvertTo-Json -Compress + + if ($ShopfloorInfo.DNCConfig) { + $postData.dncConfig = $ShopfloorInfo.DNCConfig | ConvertTo-Json -Compress + Write-Host " Sending DNC Config: $(($ShopfloorInfo.DNCConfig | ConvertTo-Json -Compress).Substring(0, 100))..." -ForegroundColor Cyan + } else { + Write-Host " No DNC Config to send (ShopfloorInfo.DNCConfig is null or empty)" -ForegroundColor Yellow + } + + # Add GE Aircraft Engines registry information + if ($ShopfloorInfo.GERegistryInfo) { + $geInfo = $ShopfloorInfo.GERegistryInfo + $postData.dncDualPathEnabled = if ($geInfo.DualPathEnabled -ne $null) { $geInfo.DualPathEnabled } else { $null } + $postData.dncPath1Name = $geInfo.Path1Name + $postData.dncPath2Name = $geInfo.Path2Name + $postData.dncGeRegistry32Bit = $geInfo.Registry32Bit + $postData.dncGeRegistry64Bit = $geInfo.Registry64Bit + $postData.dncGeRegistryNotes = $geInfo.RegistryNotes | ConvertTo-Json -Compress + + Write-Host " Sending GE Registry Info:" -ForegroundColor Cyan + Write-Host " 32-bit: $($geInfo.Registry32Bit), 64-bit: $($geInfo.Registry64Bit)" -ForegroundColor Cyan + Write-Host " DualPath: $(if ($geInfo.DualPathEnabled -ne $null) { $geInfo.DualPathEnabled } else { 'Not Found' })" -ForegroundColor Cyan + if ($geInfo.Path1Name) { Write-Host " Path1Name: $($geInfo.Path1Name)" -ForegroundColor Cyan } + if ($geInfo.Path2Name) { Write-Host " Path2Name: $($geInfo.Path2Name)" -ForegroundColor Cyan } + } else { + Write-Host " No GE Registry Info to send" -ForegroundColor Yellow + } + } else { + Write-Host " No ShopfloorInfo available" -ForegroundColor Yellow + } + + # Add installed applications data for shopfloor PCs + if ($SystemInfo.InstalledApplications -and $SystemInfo.InstalledApplications.Count -gt 0) { + $postData.installedApplications = $SystemInfo.InstalledApplications | ConvertTo-Json -Compress + Write-Host " Sending installed applications data:" -ForegroundColor Cyan + + $activeApps = $SystemInfo.InstalledApplications | Where-Object { $_.IsActive -eq $true } + if ($activeApps.Count -gt 0) { + foreach ($app in $activeApps) { + Write-Host " - $($app.AppName) (AppID: $($app.AppID)) - ACTIVE (PID: $($app.ProcessID))" -ForegroundColor Green + } + } else { + Write-Host " - No active applications detected" -ForegroundColor Gray + } + } else { + Write-Host " No installed applications to send" -ForegroundColor Gray + } + + # Send to dashboard API + $headers = @{ + 'Content-Type' = 'application/x-www-form-urlencoded' + } + + $response = Invoke-RestMethod -Uri $DashboardURL -Method Post -Body $postData -Headers $headers -TimeoutSec 30 + + if ($response.success) { + Write-Host " [OK] Complete asset data stored in database!" -ForegroundColor Green + $data = $response.data + Write-Host " PCID: $(if($data.pcid) { $data.pcid } else { 'Unknown' })" + Write-Host " Updated/Created: $(if($data.operation) { $data.operation } else { 'Unknown' })" + Write-Host " Records affected: $(if($data.recordsAffected) { $data.recordsAffected } else { 'Unknown' })" + return $true + } else { + Write-Host " [FAIL] Dashboard could not store data: $($response.error)" -ForegroundColor Red + if ($response.data -and $response.data.debug) { + Write-Host " Debug - DNC Config Received: $($response.data.debug.dncConfigReceived)" -ForegroundColor Yellow + } elseif ($response.debug) { + Write-Host " Debug - DNC Config Received: $($response.debug.dncConfigReceived)" -ForegroundColor Yellow + } + return $false + } + } + catch { + Write-Host " [FAIL] Error sending data to dashboard: $($_.Exception.Message)" -ForegroundColor Red + + # Try to get more details from the response + if ($_.Exception -is [System.Net.WebException]) { + $response = $_.Exception.Response + if ($response) { + $responseStream = $response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($responseStream) + $responseBody = $reader.ReadToEnd() + $reader.Close() + + Write-Host " Server Response:" -ForegroundColor Yellow + try { + $errorData = $responseBody | ConvertFrom-Json + if ($errorData.details) { + Write-Host " Error: $($errorData.details.message)" -ForegroundColor Red + Write-Host " File: $($errorData.details.file):$($errorData.details.line)" -ForegroundColor Gray + } else { + Write-Host " $responseBody" -ForegroundColor Gray + } + } catch { + Write-Host " $responseBody" -ForegroundColor Gray + } + } + } + + return $false + } +} + +# Main execution +try { + # Log startup + Write-Log "========================================" -Level "INFO" -ForegroundColor Green + Write-Log "Complete PC Asset Collection & Storage" -Level "INFO" -ForegroundColor Green + Write-Log "========================================" -Level "INFO" -ForegroundColor Green + Write-Log "Computer: $env:COMPUTERNAME" -Level "INFO" -ForegroundColor White + Write-Log "Log file: $script:LogFile" -Level "INFO" -ForegroundColor Gray + + # Get dashboard URL + $dashboardURL = Get-DashboardURL -ProvidedURL $DashboardURL + Write-Log "Dashboard: $dashboardURL" -Level "INFO" -ForegroundColor Gray + Write-Log "Note: Warranty lookups disabled (handled by dashboard)" -Level "INFO" -ForegroundColor Gray + Write-Log "" -Level "INFO" -ForegroundColor White + + # Test connections if requested + if ($TestConnections) { + Write-Host "=== CONNECTION TESTS ===" -ForegroundColor Cyan + + # Only test dashboard connection since proxy is not accessible from PCs + Write-Host "Skipping proxy test (not accessible from client PCs)" -ForegroundColor Gray + $dashboardOK = Test-DashboardConnection -DashboardURL $dashboardURL + + Write-Host "" + if ($dashboardOK) { + Write-Host "[OK] Dashboard connection working!" -ForegroundColor Green + exit 0 + } else { + Write-Host "[FAIL] Dashboard connection failed" -ForegroundColor Red + exit 1 + } + } + + # Step 1: Collect system information + Write-Host "=== STEP 1: COLLECT SYSTEM INFO ===" -ForegroundColor Cyan + $systemInfo = Collect-SystemInfo + + # Step 2: Collect shopfloor-specific info (if applicable) + Write-Host "" + Write-Host "=== STEP 2: COLLECT SHOPFLOOR INFO ===" -ForegroundColor Cyan + $shopfloorInfo = Collect-ShopfloorInfo -SystemInfo $systemInfo + + # Step 3: Get warranty data from proxy (if not skipped and Dell system) + $warrantyData = $null + $isDellSystem = $systemInfo.Manufacturer -match "Dell" + + Write-Host "" + Write-Host "=== STEP 3: WARRANTY DATA ===" -ForegroundColor Cyan + Write-Host "Warranty lookups disabled - Dashboard will handle warranty updates" -ForegroundColor Yellow + Write-Host "PCs cannot reach proxy server from this network" -ForegroundColor Gray + + # Step 4: Send all data to dashboard for storage + Write-Host "" + Write-Host "=== STEP 4: STORE IN DATABASE ===" -ForegroundColor Cyan + $storeSuccess = Send-CompleteDataToDashboard -SystemInfo $systemInfo -ShopfloorInfo $shopfloorInfo -WarrantyData $warrantyData -DashboardURL $dashboardURL + + if (-not $storeSuccess) { + Write-Error "Failed to store asset data in database" + exit 1 + } + + # Step 5: Collect and send default printer mapping + Write-Host "" + Write-Host "=== STEP 5: PRINTER MAPPING ===" -ForegroundColor Cyan + $defaultPrinterFQDN = Get-DefaultPrinterFQDN + $printerMappingSuccess = Send-PrinterMappingToDashboard -Hostname $systemInfo.Hostname -PrinterFQDN $defaultPrinterFQDN -DashboardURL $dashboardURL + + # Step 6: Send tracked applications to database + Write-Host "" + Write-Host "=== STEP 6: APPLICATION MAPPING ===" -ForegroundColor Cyan + $appMappingSuccess = Send-InstalledAppsToDashboard -Hostname $systemInfo.Hostname -TrackedApps $systemInfo.TrackedApplications -DashboardURL $dashboardURL + + # Check if running as admin for Steps 7 & 8 + $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") + + # Step 7: Reset and configure WinRM for remote management (requires admin) + Write-Log "" -Level "INFO" -ForegroundColor White + Write-Log "=== STEP 7: WINRM CONFIGURATION ===" -Level "INFO" -ForegroundColor Cyan + if ($isAdmin) { + $winrmSuccess = Reset-WinRMConfiguration + } else { + Write-Log " [SKIP] Not running as admin - WinRM configuration skipped" -Level "WARN" -ForegroundColor Yellow + $winrmSuccess = $false + } + + # Step 8: Add WinRM management group to local Administrators (requires admin) + Write-Log "" -Level "INFO" -ForegroundColor White + Write-Log "=== STEP 8: WINRM ADMIN GROUP ===" -Level "INFO" -ForegroundColor Cyan + if ($isAdmin) { + $adminGroupSuccess = Add-WinRMAdminGroup + } else { + Write-Log " [SKIP] Not running as admin - Admin group setup skipped" -Level "WARN" -ForegroundColor Yellow + $adminGroupSuccess = $false + } + + # Final Summary + Write-Log "" -Level "INFO" -ForegroundColor White + Write-Log "=== COMPLETE ASSET UPDATE SUCCESS ===" -Level "INFO" -ForegroundColor Green + Write-Log "Computer: $($systemInfo.Hostname)" -Level "INFO" -ForegroundColor White + Write-Log "Type: $($systemInfo.PCType)" -Level "INFO" -ForegroundColor White + Write-Log "Serial: $($systemInfo.SerialNumber)" -Level "INFO" -ForegroundColor White + if ($systemInfo.MachineNo) { + Write-Log "Machine: $($systemInfo.MachineNo)" -Level "INFO" -ForegroundColor White + } + Write-Log "" -Level "INFO" -ForegroundColor White + Write-Log "Data Collected & Stored:" -Level "INFO" -ForegroundColor Cyan + Write-Log "[OK] Basic system information" -Level "SUCCESS" -ForegroundColor Green + if ($defaultPrinterFQDN) { + Write-Log "[OK] Default printer mapping ($defaultPrinterFQDN)" -Level "SUCCESS" -ForegroundColor Green + } else { + Write-Log "[--] Default printer mapping (no printer found)" -Level "INFO" -ForegroundColor Gray + } + if ($systemInfo.TrackedApplications -and $systemInfo.TrackedApplications.Count -gt 0) { + Write-Log "[OK] Application mapping ($($systemInfo.TrackedApplications.Count) tracked apps)" -Level "SUCCESS" -ForegroundColor Green + } else { + Write-Log "[--] Application mapping (no tracked apps found)" -Level "INFO" -ForegroundColor Gray + } + + if ($shopfloorInfo) { + Write-Log "[OK] Shopfloor configurations ($($shopfloorInfo.NetworkInterfaces.Count) network, $($shopfloorInfo.CommConfigs.Count) comm)" -Level "SUCCESS" -ForegroundColor Green + } + + if ($warrantyData) { + Write-Log "[OK] Warranty information ($($warrantyData.warrantyStatus), $($warrantyData.daysRemaining) days)" -Level "SUCCESS" -ForegroundColor Green + } elseif (-not $SkipWarranty -and $isDellSystem) { + Write-Log "[WARN] Warranty information (lookup failed)" -Level "WARN" -ForegroundColor Yellow + } + + if ($winrmSuccess) { + Write-Log "[OK] WinRM HTTP listener (port 5985)" -Level "SUCCESS" -ForegroundColor Green + Write-Log " Note: If remote access still fails, a reboot may be required" -Level "INFO" -ForegroundColor Gray + } else { + Write-Log "[WARN] WinRM configuration (may need manual setup)" -Level "WARN" -ForegroundColor Yellow + } + + if ($adminGroupSuccess) { + Write-Log "[OK] WinRM admin group (logon\g03078610)" -Level "SUCCESS" -ForegroundColor Green + } else { + Write-Log "[WARN] WinRM admin group (failed to add)" -Level "WARN" -ForegroundColor Yellow + } + + Write-Log "" -Level "INFO" -ForegroundColor White + Write-Log "[OK] Complete PC asset collection finished!" -Level "SUCCESS" -ForegroundColor Green + Write-Log "All data stored in database via dashboard API." -Level "INFO" -ForegroundColor Gray + Write-Log "Log file: $script:LogFile" -Level "INFO" -ForegroundColor Gray + +} catch { + Write-Log "Complete asset collection failed: $($_.Exception.Message)" -Level "ERROR" -ForegroundColor Red + Write-Error "Complete asset collection failed: $($_.Exception.Message)" + exit 1 +} \ No newline at end of file