diff --git a/scripts/Update-ShopfloorPCs-Remote.ps1 b/scripts/Update-ShopfloorPCs-Remote.ps1 new file mode 100644 index 0000000..8b250dd --- /dev/null +++ b/scripts/Update-ShopfloorPCs-Remote.ps1 @@ -0,0 +1,784 @@ +<# +.SYNOPSIS + Remotely collects PC information from shopfloor PCs via WinRM and updates ShopDB. + +.DESCRIPTION + This script uses WinRM (Invoke-Command) to remotely execute commands on shopfloor PCs, + collect system information, and POST it to the ShopDB API. + +.PARAMETER ComputerName + Single computer name or array of computer names to update. + +.PARAMETER All + Query ShopDB for all shopfloor PCs and update them. + +.PARAMETER Credential + PSCredential object for authentication. If not provided, will prompt or use current user. + +.PARAMETER ApiUrl + URL to the ShopDB API. Defaults to http://192.168.122.151:8080/api.asp + +.EXAMPLE + # Update a single PC + .\Update-ShopfloorPCs-Remote.ps1 -ComputerName "SHOPFLOOR-PC01" + +.EXAMPLE + # Update multiple PCs + .\Update-ShopfloorPCs-Remote.ps1 -ComputerName "PC01","PC02","PC03" + +.EXAMPLE + # Update all shopfloor PCs from ShopDB + .\Update-ShopfloorPCs-Remote.ps1 -All + +.EXAMPLE + # With specific credentials + $cred = Get-Credential + .\Update-ShopfloorPCs-Remote.ps1 -ComputerName "SHOPFLOOR-PC01" -Credential $cred + +.NOTES + Requires: + - WinRM enabled on target PCs (Enable-PSRemoting) + - Admin credentials on target PCs + - Network access to target PCs on port 5985 (HTTP) or 5986 (HTTPS) +#> + +[CmdletBinding(DefaultParameterSetName='ByName')] +param( + [Parameter(ParameterSetName='ByName', Position=0)] + [string[]]$ComputerName, + + [Parameter(ParameterSetName='All')] + [switch]$All, + + [Parameter(ParameterSetName='SetupTrust')] + [switch]$SetupTrustedHosts, + + [Parameter()] + [PSCredential]$Credential, + + [Parameter()] + [string]$ApiUrl = "https://tsgwp00525.rd.ds.ge.com/shopdb/api.asp", + + [Parameter()] + [string]$DnsSuffix = "logon.ds.ge.com", + + [Parameter()] + [switch]$SkipDnsLookup, + + [Parameter()] + [switch]$UseSSL, + + [Parameter()] + [int]$ThrottleLimit = 10, + + [Parameter()] + [switch]$WhatIf +) + +#region Functions + +function Write-Log { + param([string]$Message, [string]$Level = "INFO") + $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + $color = switch ($Level) { + "ERROR" { "Red" } + "WARNING" { "Yellow" } + "SUCCESS" { "Green" } + default { "White" } + } + Write-Host "[$timestamp] [$Level] $Message" -ForegroundColor $color +} + +function Get-ShopfloorPCsFromApi { + <# + .SYNOPSIS + Queries ShopDB API to get list of shopfloor PCs with details. + #> + param([string]$ApiUrl) + + try { + Write-Log "Querying API: $ApiUrl`?action=getShopfloorPCs" -Level "INFO" + $response = Invoke-RestMethod -Uri "$ApiUrl`?action=getShopfloorPCs" -Method Get -ErrorAction Stop + + if ($response.success -and $response.data) { + Write-Log "API returned $($response.count) shopfloor PCs" -Level "SUCCESS" + + # Return full PC objects, not just hostnames + return $response.data + } else { + Write-Log "No shopfloor PCs returned from API" -Level "WARNING" + return @() + } + } catch { + Write-Log "Failed to query API for shopfloor PCs: $_" -Level "ERROR" + return @() + } +} + +function Get-PCConnectionInfo { + <# + .SYNOPSIS + Builds FQDN and resolves IP for a PC hostname. + #> + param( + [Parameter(Mandatory)] + [string]$Hostname, + + [Parameter()] + [string]$DnsSuffix = "logon.ds.ge.com", + + [Parameter()] + [switch]$SkipDnsLookup + ) + + $result = @{ + Hostname = $Hostname + FQDN = $null + IPAddress = $null + Reachable = $false + Error = $null + } + + # Build FQDN - if hostname already contains dots, assume it's already an FQDN + if ($Hostname -like "*.*") { + $result.FQDN = $Hostname + } else { + $result.FQDN = "$Hostname.$DnsSuffix" + } + + if (-not $SkipDnsLookup) { + try { + # Resolve DNS to get current IP + $dnsResult = Resolve-DnsName -Name $result.FQDN -Type A -ErrorAction Stop + $result.IPAddress = ($dnsResult | Where-Object { $_.Type -eq 'A' } | Select-Object -First 1).IPAddress + + if ($result.IPAddress) { + # Quick connectivity test (WinRM port 5985) + $tcpTest = Test-NetConnection -ComputerName $result.FQDN -Port 5985 -WarningAction SilentlyContinue + $result.Reachable = $tcpTest.TcpTestSucceeded + } + } catch { + $result.Error = "DNS lookup failed: $($_.Exception.Message)" + } + } else { + # Skip DNS lookup, just use FQDN directly + $result.Reachable = $true # Assume reachable, let WinRM fail if not + } + + return $result +} + +function Test-WinRMConnectivity { + <# + .SYNOPSIS + Tests WinRM connectivity to a remote PC. + #> + param( + [Parameter(Mandatory)] + [string]$ComputerName, + + [Parameter()] + [PSCredential]$Credential + ) + + $testParams = @{ + ComputerName = $ComputerName + ErrorAction = 'Stop' + } + if ($Credential) { + $testParams.Credential = $Credential + } + + try { + $result = Test-WSMan @testParams + return @{ Success = $true; Error = $null } + } catch { + return @{ Success = $false; Error = $_.Exception.Message } + } +} + +function Add-TrustedHosts { + <# + .SYNOPSIS + Adds computers to WinRM TrustedHosts list. + #> + param( + [Parameter()] + [string[]]$ComputerNames, + + [Parameter()] + [string]$DnsSuffix = "logon.ds.ge.com", + + [Parameter()] + [switch]$TrustAllInDomain + ) + + # Check if running as admin + $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) + if (-not $isAdmin) { + Write-Log "ERROR: Must run as Administrator to modify TrustedHosts" -Level "ERROR" + return $false + } + + try { + # Get current trusted hosts + $currentHosts = (Get-Item WSMan:\localhost\Client\TrustedHosts -ErrorAction SilentlyContinue).Value + Write-Log "Current TrustedHosts: $(if ($currentHosts) { $currentHosts } else { '(empty)' })" -Level "INFO" + + if ($TrustAllInDomain) { + # Trust all hosts in the domain (wildcard) + $newValue = "*.$DnsSuffix" + Write-Log "Adding wildcard trust for: $newValue" -Level "INFO" + } else { + # Build list of FQDNs to add + $fqdnsToAdd = @() + foreach ($computer in $ComputerNames) { + if ($computer -like "*.*") { + $fqdnsToAdd += $computer + } else { + $fqdnsToAdd += "$computer.$DnsSuffix" + } + } + + # Merge with existing + $existingList = if ($currentHosts) { $currentHosts -split ',' } else { @() } + $mergedList = ($existingList + $fqdnsToAdd) | Select-Object -Unique + $newValue = $mergedList -join ',' + + Write-Log "Adding to TrustedHosts: $($fqdnsToAdd -join ', ')" -Level "INFO" + } + + # Set the new value + Set-Item WSMan:\localhost\Client\TrustedHosts -Value $newValue -Force + Write-Log "TrustedHosts updated successfully" -Level "SUCCESS" + + # Show new value + $updatedHosts = (Get-Item WSMan:\localhost\Client\TrustedHosts).Value + Write-Log "New TrustedHosts: $updatedHosts" -Level "INFO" + + return $true + } catch { + Write-Log "Failed to update TrustedHosts: $_" -Level "ERROR" + return $false + } +} + +function Show-TrustedHostsHelp { + <# + .SYNOPSIS + Shows help for setting up TrustedHosts. + #> + + Write-Host "" + Write-Host "========================================" -ForegroundColor Cyan + Write-Host " WinRM TrustedHosts Setup Guide" -ForegroundColor Cyan + Write-Host "========================================" -ForegroundColor Cyan + Write-Host "" + Write-Host "Since you're not on the same domain as the shopfloor PCs," -ForegroundColor White + Write-Host "you need to add them to your TrustedHosts list." -ForegroundColor White + Write-Host "" + Write-Host "OPTION 1: Trust all PCs in the domain (Recommended)" -ForegroundColor Yellow + Write-Host " Run as Administrator:" -ForegroundColor Gray + Write-Host ' Set-Item WSMan:\localhost\Client\TrustedHosts -Value "*.logon.ds.ge.com" -Force' -ForegroundColor Green + Write-Host "" + Write-Host "OPTION 2: Trust specific PCs" -ForegroundColor Yellow + Write-Host " Run as Administrator:" -ForegroundColor Gray + Write-Host ' Set-Item WSMan:\localhost\Client\TrustedHosts -Value "PC01.logon.ds.ge.com,PC02.logon.ds.ge.com" -Force' -ForegroundColor Green + Write-Host "" + Write-Host "OPTION 3: Use this script to set it up" -ForegroundColor Yellow + Write-Host " # Trust all in domain:" -ForegroundColor Gray + Write-Host " .\Update-ShopfloorPCs-Remote.ps1 -SetupTrustedHosts" -ForegroundColor Green + Write-Host "" + Write-Host " # Trust specific PCs:" -ForegroundColor Gray + Write-Host ' .\Update-ShopfloorPCs-Remote.ps1 -SetupTrustedHosts -ComputerName "PC01","PC02"' -ForegroundColor Green + Write-Host "" + Write-Host "VIEW CURRENT SETTINGS:" -ForegroundColor Yellow + Write-Host ' Get-Item WSMan:\localhost\Client\TrustedHosts' -ForegroundColor Green + Write-Host "" + Write-Host "CLEAR TRUSTEDHOSTS:" -ForegroundColor Yellow + Write-Host ' Set-Item WSMan:\localhost\Client\TrustedHosts -Value "" -Force' -ForegroundColor Green + Write-Host "" + Write-Host "========================================" -ForegroundColor Cyan + Write-Host "" +} + +function Get-RemotePCInfo { + <# + .SYNOPSIS + Script block to execute on remote PC to collect system information. + #> + + # This scriptblock runs on the REMOTE computer + $scriptBlock = { + $result = @{ + Success = $false + Hostname = $env:COMPUTERNAME + Error = $null + } + + try { + # Get basic system info + $computerSystem = Get-CimInstance -ClassName Win32_ComputerSystem + $bios = Get-CimInstance -ClassName Win32_BIOS + $os = Get-CimInstance -ClassName Win32_OperatingSystem + + $result.SerialNumber = $bios.SerialNumber + $result.Manufacturer = $computerSystem.Manufacturer + $result.Model = $computerSystem.Model + $result.LoggedInUser = $computerSystem.UserName + $result.OSVersion = $os.Caption + + # Get network interfaces + $networkAdapters = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration | + Where-Object { $_.IPEnabled -eq $true } + + $networkInterfaces = @() + foreach ($adapter in $networkAdapters) { + if ($adapter.IPAddress) { + foreach ($i in 0..($adapter.IPAddress.Count - 1)) { + $ip = $adapter.IPAddress[$i] + # Only include IPv4 addresses + if ($ip -match '^\d+\.\d+\.\d+\.\d+$') { + $networkInterfaces += @{ + IPAddress = $ip + MACAddress = $adapter.MACAddress + SubnetMask = if ($adapter.IPSubnet) { $adapter.IPSubnet[$i] } else { "" } + DefaultGateway = if ($adapter.DefaultIPGateway) { $adapter.DefaultIPGateway[0] } else { "" } + InterfaceName = $adapter.Description + IsMachineNetwork = ($ip -like "192.168.*" -or $ip -like "10.0.*") + } + } + } + } + } + $result.NetworkInterfaces = $networkInterfaces + + # Get DNC configuration if it exists + $dncConfig = @{} + $dncIniPath = "C:\DNC\DNC.ini" + if (Test-Path $dncIniPath) { + $iniContent = Get-Content $dncIniPath -Raw + # Parse INI file for common DNC settings + if ($iniContent -match 'Site\s*=\s*(.+)') { $dncConfig.Site = $matches[1].Trim() } + if ($iniContent -match 'CNC\s*=\s*(.+)') { $dncConfig.CNC = $matches[1].Trim() } + if ($iniContent -match 'NCIF\s*=\s*(.+)') { $dncConfig.NCIF = $matches[1].Trim() } + if ($iniContent -match 'MachineNumber\s*=\s*(.+)') { $dncConfig.MachineNumber = $matches[1].Trim() } + if ($iniContent -match 'FTPHost\s*=\s*(.+)') { $dncConfig.FTPHostPrimary = $matches[1].Trim() } + } + $result.DNCConfig = $dncConfig + + # Get machine number from registry or DNC config + $machineNo = $null + # Try registry first + $regPath = "HKLM:\SOFTWARE\GE\ShopFloor" + if (Test-Path $regPath) { + $machineNo = (Get-ItemProperty -Path $regPath -Name "MachineNumber" -ErrorAction SilentlyContinue).MachineNumber + } + # Fall back to DNC config + if (-not $machineNo -and $dncConfig.MachineNumber) { + $machineNo = $dncConfig.MachineNumber + } + $result.MachineNo = $machineNo + + # Get serial port configuration + $comPorts = @() + $serialPorts = Get-CimInstance -ClassName Win32_SerialPort -ErrorAction SilentlyContinue + foreach ($port in $serialPorts) { + $comPorts += @{ + PortName = $port.DeviceID + Description = $port.Description + } + } + $result.SerialPorts = $comPorts + + # Check for VNC installation + $hasVnc = $false + $regPaths = @( + "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*", + "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" + ) + foreach ($path in $regPaths) { + if (Test-Path $path) { + $vncApps = Get-ItemProperty $path -ErrorAction SilentlyContinue | + Where-Object { $_.DisplayName -like "*VNC Server*" -or $_.DisplayName -like "*VNC Connect*" -or $_.DisplayName -like "*RealVNC*" } + if ($vncApps) { + $hasVnc = $true + break + } + } + } + if (-not $hasVnc) { + $vncService = Get-Service -Name "vncserver*" -ErrorAction SilentlyContinue + if ($vncService) { $hasVnc = $true } + } + $result.HasVnc = $hasVnc + + # Check for PC-DMIS installation (CMM software) + $hasPcDmis = $false + foreach ($path in $regPaths) { + if (Test-Path $path) { + $pcDmisApps = Get-ItemProperty $path -ErrorAction SilentlyContinue | + Where-Object { $_.DisplayName -like "*PC-DMIS*" -or $_.DisplayName -like "*PCDMIS*" } + if ($pcDmisApps) { + $hasPcDmis = $true + break + } + } + } + # 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*" + ) + foreach ($dmisPath in $pcDmisPaths) { + if (Test-Path $dmisPath) { + $hasPcDmis = $true + break + } + } + } + $result.HasPcDmis = $hasPcDmis + + $result.Success = $true + } catch { + $result.Error = $_.Exception.Message + } + + return $result + } + + return $scriptBlock +} + +function Send-PCDataToApi { + <# + .SYNOPSIS + Sends collected PC data to the ShopDB API. + #> + param( + [Parameter(Mandatory)] + [hashtable]$PCData, + + [Parameter(Mandatory)] + [string]$ApiUrl + ) + + try { + # Determine PC type based on machine number + $pcType = if ($PCData.MachineNo) { "Shopfloor" } else { "Standard" } + + # Build the POST body + $postData = @{ + action = 'updateCompleteAsset' + hostname = $PCData.Hostname + serialNumber = $PCData.SerialNumber + manufacturer = $PCData.Manufacturer + model = $PCData.Model + pcType = $pcType + loggedInUser = $PCData.LoggedInUser + osVersion = $PCData.OSVersion + } + + # Add machine number if available + if ($PCData.MachineNo) { + $postData.machineNo = $PCData.MachineNo + } + + # Add VNC status + if ($PCData.HasVnc) { + $postData.hasVnc = "1" + } else { + $postData.hasVnc = "0" + } + + # Add network interfaces as JSON + if ($PCData.NetworkInterfaces -and $PCData.NetworkInterfaces.Count -gt 0) { + $postData.networkInterfaces = ($PCData.NetworkInterfaces | ConvertTo-Json -Compress) + } + + # Add DNC config if available + if ($PCData.DNCConfig -and $PCData.DNCConfig.Keys.Count -gt 0) { + $postData.dncConfig = ($PCData.DNCConfig | ConvertTo-Json -Compress) + } + + # Send to API + $response = Invoke-RestMethod -Uri $ApiUrl -Method Post -Body $postData -ErrorAction Stop + + return @{ + Success = $response.success + Message = $response.message + MachineId = $response.machineid + } + } catch { + return @{ + Success = $false + Message = $_.Exception.Message + MachineId = $null + } + } +} + +#endregion Functions + +#region Main + +# Handle TrustedHosts setup mode +if ($SetupTrustedHosts) { + Write-Log "=== TrustedHosts Setup Mode ===" -Level "INFO" + + if ($ComputerName -and $ComputerName.Count -gt 0) { + # Trust specific computers + $result = Add-TrustedHosts -ComputerNames $ComputerName -DnsSuffix $DnsSuffix + } else { + # Trust all in domain (wildcard) + $result = Add-TrustedHosts -DnsSuffix $DnsSuffix -TrustAllInDomain + } + + if (-not $result) { + Show-TrustedHostsHelp + } + exit 0 +} + +Write-Log "=== ShopDB Remote PC Update Script ===" -Level "INFO" +Write-Log "API URL: $ApiUrl" -Level "INFO" +Write-Log "DNS Suffix: $DnsSuffix" -Level "INFO" + +# Prompt for credentials if not provided +if (-not $Credential) { + Write-Log "No credentials provided. Prompting for credentials..." -Level "INFO" + $Credential = Get-Credential -Message "Enter credentials for remote PCs" + if (-not $Credential) { + Write-Log "Credentials required. Exiting." -Level "ERROR" + exit 1 + } +} + +# Show credential info (username only, not password) +Write-Log "Using credentials: $($Credential.UserName)" -Level "INFO" + +# Determine list of computers to process +$computers = @() + +if ($All) { + Write-Log "Querying ShopDB for all shopfloor PCs..." -Level "INFO" + $shopfloorPCs = Get-ShopfloorPCsFromApi -ApiUrl $ApiUrl + if ($shopfloorPCs.Count -eq 0) { + Write-Log "No shopfloor PCs found in ShopDB. Exiting." -Level "WARNING" + exit 0 + } + + # Display summary table + Write-Host "" + Write-Host " Shopfloor PCs from ShopDB:" -ForegroundColor Cyan + Write-Host " $("-" * 90)" -ForegroundColor Gray + Write-Host (" {0,-20} {1,-15} {2,-15} {3,-20} {4,-15}" -f "Hostname", "Machine #", "IP Address", "Last Updated", "Logged In") -ForegroundColor Cyan + Write-Host " $("-" * 90)" -ForegroundColor Gray + + foreach ($pc in $shopfloorPCs) { + $lastUpdated = if ($pc.lastupdated) { $pc.lastupdated } else { "Never" } + $loggedIn = if ($pc.loggedinuser) { $pc.loggedinuser } else { "-" } + $machineNo = if ($pc.machinenumber) { $pc.machinenumber } else { "-" } + $ipAddr = if ($pc.ipaddress) { $pc.ipaddress } else { "-" } + + Write-Host (" {0,-20} {1,-15} {2,-15} {3,-20} {4,-15}" -f $pc.hostname, $machineNo, $ipAddr, $lastUpdated, $loggedIn) + } + Write-Host " $("-" * 90)" -ForegroundColor Gray + Write-Host "" + + # Extract hostnames for processing + $computers = $shopfloorPCs | ForEach-Object { $_.hostname } | Where-Object { $_ -ne "" -and $_ -ne $null } + Write-Log "Found $($computers.Count) shopfloor PCs with hostnames" -Level "INFO" + +} elseif ($ComputerName) { + $computers = $ComputerName +} else { + Write-Log "No computers specified. Use -ComputerName or -All parameter." -Level "ERROR" + Write-Host "" + Write-Host "Usage Examples:" -ForegroundColor Yellow + Write-Host " # Update all shopfloor PCs from database:" -ForegroundColor Gray + Write-Host " .\Update-ShopfloorPCs-Remote.ps1 -All -Credential `$cred" -ForegroundColor Green + Write-Host "" + Write-Host " # Update specific PC:" -ForegroundColor Gray + Write-Host " .\Update-ShopfloorPCs-Remote.ps1 -ComputerName 'PC01' -Credential `$cred" -ForegroundColor Green + Write-Host "" + Write-Host " # Setup TrustedHosts first (run as admin):" -ForegroundColor Gray + Write-Host " .\Update-ShopfloorPCs-Remote.ps1 -SetupTrustedHosts" -ForegroundColor Green + Write-Host "" + exit 1 +} + +Write-Log "Processing $($computers.Count) computer(s)..." -Level "INFO" + +# Build FQDNs directly - skip slow DNS/connectivity checks +# Let WinRM handle connection failures (much faster) +Write-Log "Building FQDN list..." -Level "INFO" +$targetComputers = @() + +foreach ($computer in $computers) { + # Build FQDN - if hostname already contains dots, assume it's already an FQDN + $fqdn = if ($computer -like "*.*") { $computer } else { "$computer.$DnsSuffix" } + + $targetComputers += @{ + Hostname = $computer + FQDN = $fqdn + IPAddress = $null + } +} + +if ($targetComputers.Count -eq 0) { + Write-Log "No computers to process. Exiting." -Level "ERROR" + exit 1 +} + +Write-Log "Will attempt connection to $($targetComputers.Count) PC(s)" -Level "INFO" +$skippedComputers = @() + +if ($WhatIf) { + Write-Log "WhatIf mode - no changes will be made" -Level "WARNING" + Write-Log "Would process: $($targetComputers.FQDN -join ', ')" -Level "INFO" + exit 0 +} + +# Build session options - use FQDNs for WinRM connection +$fqdnList = $targetComputers | ForEach-Object { $_.FQDN } + +# Create session options with short timeout (15 seconds per PC) +$sessionOption = New-PSSessionOption -OpenTimeout 15000 -OperationTimeout 30000 -NoMachineProfile + +$sessionParams = @{ + ComputerName = $fqdnList + ScriptBlock = (Get-RemotePCInfo) + SessionOption = $sessionOption + Authentication = 'Negotiate' + ErrorAction = 'SilentlyContinue' + ErrorVariable = 'remoteErrors' +} + +if ($Credential) { + $sessionParams.Credential = $Credential + Write-Log "Credential added to session params: $($Credential.UserName)" -Level "INFO" +} else { + Write-Log "WARNING: No credential in session params!" -Level "WARNING" +} + +if ($ThrottleLimit -and $PSVersionTable.PSVersion.Major -ge 7) { + $sessionParams.ThrottleLimit = $ThrottleLimit +} + +# Execute remote commands (runs in parallel by default) +Write-Log "Connecting to remote PCs via WinRM (timeout: 15s per PC)..." -Level "INFO" +$results = Invoke-Command @sessionParams + +# Process results +$successCount = 0 +$failCount = 0 +$successPCs = @() +$failedPCs = @() + +Write-Host "" +Write-Log "Processing WinRM results..." -Level "INFO" + +foreach ($result in $results) { + if ($result.Success) { + Write-Log "[OK] $($result.Hostname)" -Level "SUCCESS" + Write-Log " Serial: $($result.SerialNumber) | Model: $($result.Model) | OS: $($result.OSVersion)" -Level "INFO" + + if ($result.NetworkInterfaces -and $result.NetworkInterfaces.Count -gt 0) { + $ips = ($result.NetworkInterfaces | ForEach-Object { $_.IPAddress }) -join ", " + Write-Log " IPs: $ips" -Level "INFO" + } + + if ($result.MachineNo) { + Write-Log " Machine #: $($result.MachineNo)" -Level "INFO" + } + + # Pass the result hashtable directly to the API function + # Note: $result from Invoke-Command is already a hashtable, + # PSObject.Properties gives metadata (Keys, Values, Count) not the actual data + + # Send to API + Write-Log " Sending to API..." -Level "INFO" + $apiResult = Send-PCDataToApi -PCData $result -ApiUrl $ApiUrl + + if ($apiResult.Success) { + Write-Log " -> Updated in ShopDB (MachineID: $($apiResult.MachineId))" -Level "SUCCESS" + $successCount++ + $successPCs += @{ + Hostname = $result.Hostname + MachineId = $apiResult.MachineId + Serial = $result.SerialNumber + } + } else { + Write-Log " -> API Error: $($apiResult.Message)" -Level "ERROR" + $failCount++ + $failedPCs += @{ + Hostname = $result.Hostname + Error = "API: $($apiResult.Message)" + } + } + } else { + Write-Log "[FAIL] $($result.Hostname): $($result.Error)" -Level "ERROR" + $failCount++ + $failedPCs += @{ + Hostname = $result.Hostname + Error = $result.Error + } + } + Write-Host "" +} + +# Report any connection errors +foreach ($err in $remoteErrors) { + $targetPC = if ($err.TargetObject) { $err.TargetObject } else { "Unknown" } + Write-Log "[FAIL] ${targetPC}: $($err.Exception.Message)" -Level "ERROR" + $failCount++ + $failedPCs += @{ + Hostname = $targetPC + Error = $err.Exception.Message + } +} + +# Final Summary +Write-Host "" +Write-Host "=" * 70 -ForegroundColor Cyan +Write-Host " SUMMARY" -ForegroundColor Cyan +Write-Host "=" * 70 -ForegroundColor Cyan +Write-Host "" + +Write-Host " Total Processed: $($successCount + $failCount)" -ForegroundColor White +Write-Host " Successful: $successCount" -ForegroundColor Green +Write-Host " Failed: $failCount" -ForegroundColor $(if ($failCount -gt 0) { "Red" } else { "White" }) +Write-Host " Skipped (DNS): $($skippedComputers.Count)" -ForegroundColor $(if ($skippedComputers.Count -gt 0) { "Yellow" } else { "White" }) +Write-Host "" + +if ($successPCs.Count -gt 0) { + Write-Host " Successfully Updated:" -ForegroundColor Green + foreach ($pc in $successPCs) { + Write-Host " - $($pc.Hostname) (ID: $($pc.MachineId))" -ForegroundColor Gray + } + Write-Host "" +} + +if ($failedPCs.Count -gt 0) { + Write-Host " Failed:" -ForegroundColor Red + foreach ($pc in $failedPCs) { + Write-Host " - $($pc.Hostname): $($pc.Error)" -ForegroundColor Gray + } + Write-Host "" +} + +if ($skippedComputers.Count -gt 0) { + Write-Host " Skipped (DNS/Connectivity):" -ForegroundColor Yellow + foreach ($pc in $skippedComputers) { + Write-Host " - $pc" -ForegroundColor Gray + } + Write-Host "" +} + +Write-Host "=" * 70 -ForegroundColor Cyan + +#endregion Main diff --git a/search.asp b/search.asp index 628fbcd..953f95e 100644 --- a/search.asp +++ b/search.asp @@ -63,17 +63,33 @@ Call HandleValidationError("default.asp", "INVALID_INPUT") End If -' ------------------------------- Search For Machine Number ---------------------------------------------------------- +' ------------------------------- Search For Machine Number or Hostname ---------------------------------------------------------- - strSQL = "SELECT machineid FROM machines WHERE (machinenumber = ? OR alias LIKE ?) AND machines.isactive = 1" - Set rs = ExecuteParameterizedQuery(objConn, strSQL, Array(search, "%" & search & "%")) + ' Also search by hostname for PCs, and get machinetypeid from models table to determine PC vs Equipment + strSQL = "SELECT m.machineid, m.hostname, mo.machinetypeid FROM machines m " & _ + "LEFT JOIN models mo ON m.modelnumberid = mo.modelnumberid " & _ + "WHERE (m.machinenumber = ? OR m.alias LIKE ? OR m.hostname = ?) AND m.isactive = 1" + Set rs = ExecuteParameterizedQuery(objConn, strSQL, Array(search, "%" & search & "%", search)) If Not rs.EOF Then machineid = rs("machineid") + Dim searchMachTypeId, searchIsPC, searchRedirectPage, searchHostname + searchMachTypeId = 0 + If Not IsNull(rs("machinetypeid")) Then searchMachTypeId = CLng(rs("machinetypeid")) + searchHostname = rs("hostname") & "" + ' PC if machinetypeid 33-43, OR if machinetypeid is 1 (default) and has a hostname + searchIsPC = (searchMachTypeId >= 33 And searchMachTypeId <= 43) Or (searchMachTypeId = 1 And searchHostname <> "") + rs.Close Set rs = Nothing Call CleanupResources() - Response.Redirect "./displaymachine.asp?machineid=" & machineid + + ' Route to appropriate detail page based on PC vs Equipment + If searchIsPC Then + Response.Redirect "./displaypc.asp?machineid=" & machineid + Else + Response.Redirect "./displaymachine.asp?machineid=" & machineid + End If Response.End End If @@ -477,30 +493,46 @@ rsNotif.Close Set rsNotif = Nothing ' Now get Machines (by machine number, alias, notes, machine type, or vendor) +' NOTE: machinetypeid is now sourced from models table (models.machinetypeid) not machines table +' Also search by hostname for PCs Dim rsMachines -strSQL = "SELECT m.machineid, m.machinenumber, m.alias, mt.machinetype " & _ +strSQL = "SELECT m.machineid, m.machinenumber, m.alias, m.hostname, mo.machinetypeid, mt.machinetype " & _ "FROM machines m " & _ - "INNER JOIN machinetypes mt ON m.machinetypeid = mt.machinetypeid " & _ "LEFT JOIN models mo ON m.modelnumberid = mo.modelnumberid " & _ + "LEFT JOIN machinetypes mt ON mo.machinetypeid = mt.machinetypeid " & _ "LEFT JOIN vendors v ON mo.vendorid = v.vendorid " & _ - "WHERE (m.machinenumber LIKE ? OR m.alias LIKE ? OR m.machinenotes LIKE ? OR mt.machinetype LIKE ? OR v.vendor LIKE ?) " & _ + "WHERE (m.machinenumber LIKE ? OR m.alias LIKE ? OR m.machinenotes LIKE ? OR mt.machinetype LIKE ? OR v.vendor LIKE ? OR m.hostname LIKE ?) " & _ " AND m.isactive = 1 " & _ "LIMIT 10" -Set rsMachines = ExecuteParameterizedQuery(objConn, strSQL, Array("%" & searchTerm & "%", "%" & searchTerm & "%", "%" & searchTerm & "%", "%" & searchTerm & "%", "%" & searchTerm & "%")) +Set rsMachines = ExecuteParameterizedQuery(objConn, strSQL, Array("%" & searchTerm & "%", "%" & searchTerm & "%", "%" & searchTerm & "%", "%" & searchTerm & "%", "%" & searchTerm & "%", "%" & searchTerm & "%")) Response.Write("") Do While Not rsMachines.EOF And totalCount < 100 - machineDispText = rsMachines("machinenumber") - If Not IsNull(rsMachines("alias")) And rsMachines("alias") <> "" Then - machineDispText = machineDispText & " (" & rsMachines("alias") & ")" + ' Determine display text - use hostname for PCs, machinenumber for equipment + ' PCs have machinetypeid 33-43, OR machinetypeid 1 (default) with a hostname + Dim isPC, machTypeId, loopHostname + machTypeId = 0 + If Not IsNull(rsMachines("machinetypeid")) Then machTypeId = CLng(rsMachines("machinetypeid")) + loopHostname = rsMachines("hostname") & "" + isPC = (machTypeId >= 33 And machTypeId <= 43) Or (machTypeId = 1 And loopHostname <> "") + + If isPC Then + machineDispText = rsMachines("hostname") & "" + If machineDispText = "" Then machineDispText = rsMachines("machinenumber") & "" + Else + machineDispText = rsMachines("machinenumber") & "" + If Not IsNull(rsMachines("alias")) And rsMachines("alias") <> "" Then + machineDispText = machineDispText & " (" & rsMachines("alias") & ")" + End If End If - Response.Write("") + Response.Write("") appResults(totalCount, 0) = "machine" - appResults(totalCount, 1) = rsMachines("machineid") & "|" & machineDispText & "|" & rsMachines("machinetype") + ' Include isPC flag in data: machineid|machineDisplay|machinetype|isPC + appResults(totalCount, 1) = rsMachines("machineid") & "|" & machineDispText & "|" & rsMachines("machinetype") & "|" & CStr(isPC) ' Score of 15 for machines (moderate priority) appResults(totalCount, 2) = 15 appResults(totalCount, 3) = rsMachines("machineid") @@ -708,10 +740,27 @@ Next Response.Write("