Documentation: - Add ShopDB-API.md with full API reference (all GET/POST endpoints) - Add detailed docs for Update-ShopfloorPCs-Remote, Invoke-RemoteMaintenance, Update-PC-CompleteAsset - Add DATA_COLLECTION_PARITY.md comparing local vs remote data collection - Add HTML versions of all documentation with styled code blocks - Document software deployment mechanism and how to add new apps - Document deprecated scripts (Invoke-RemoteAssetCollection, Install-KioskApp) Script Updates: - Update deployment source paths to network share (tsgwp00525.wjs.geaerospace.net) - InstallDashboard: \\...\scripts\Dashboard\GEAerospaceDashboardSetup.exe - InstallLobbyDisplay: \\...\scripts\LobbyDisplay\GEAerospaceLobbyDisplaySetup.exe - UpdateEMxAuthToken: \\...\scripts\eMx\eMxInfo.txt - DeployUDCWebServerConfig: \\...\scripts\UDC\udc_webserver_settings.json - Update machine network detection to include 100.0.0.* for CMM cases - Rename PC Type #9 from "Part Marker" to "Inspection" Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1686 lines
68 KiB
PowerShell
1686 lines
68 KiB
PowerShell
<#
|
|
.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
|
|
|
|
.EXAMPLE
|
|
# Reboot all PCs with uptime >= 30 days (will prompt for confirmation)
|
|
.\Update-ShopfloorPCs-Remote.ps1 -Reboot -MinUptimeDays 30
|
|
|
|
.EXAMPLE
|
|
# Preview which PCs would be rebooted (no actual reboot)
|
|
.\Update-ShopfloorPCs-Remote.ps1 -Reboot -MinUptimeDays 30 -WhatIf
|
|
|
|
.EXAMPLE
|
|
# Reboot PCs with uptime >= 60 days without confirmation prompt
|
|
.\Update-ShopfloorPCs-Remote.ps1 -Reboot -MinUptimeDays 60 -Force -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(ParameterSetName='Reboot', Mandatory)]
|
|
[switch]$Reboot,
|
|
|
|
[Parameter(ParameterSetName='Reboot', Mandatory)]
|
|
[int]$MinUptimeDays,
|
|
|
|
[Parameter(ParameterSetName='Reboot')]
|
|
[switch]$Force,
|
|
|
|
[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 = 25,
|
|
|
|
[Parameter()]
|
|
[switch]$WhatIf
|
|
)
|
|
|
|
# SSL/TLS Configuration - MUST be set before any web requests
|
|
# Set ALL modern TLS versions - fixes "underlying connection was closed" errors
|
|
try {
|
|
# Enable all available TLS protocols (1.0, 1.1, 1.2, 1.3 if available)
|
|
$protocols = [Net.SecurityProtocolType]::Tls12
|
|
try { $protocols = $protocols -bor [Net.SecurityProtocolType]::Tls13 } catch {}
|
|
try { $protocols = $protocols -bor [Net.SecurityProtocolType]::Tls11 } catch {}
|
|
try { $protocols = $protocols -bor [Net.SecurityProtocolType]::Tls } catch {}
|
|
[Net.ServicePointManager]::SecurityProtocol = $protocols
|
|
} catch {
|
|
# Absolute fallback
|
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
}
|
|
|
|
# Increase connection limits and timeouts
|
|
[Net.ServicePointManager]::DefaultConnectionLimit = 100
|
|
[Net.ServicePointManager]::Expect100Continue = $false
|
|
[Net.ServicePointManager]::MaxServicePointIdleTime = 10000
|
|
|
|
# Certificate Bypass for HTTPS connections (self-signed certs) - PowerShell 5.x
|
|
if ($PSVersionTable.PSVersion.Major -lt 7) {
|
|
try {
|
|
if (-not ([System.Management.Automation.PSTypeName]'TrustAllCertsPolicy').Type) {
|
|
Add-Type @"
|
|
using System.Net;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
public class TrustAllCertsPolicy : ICertificatePolicy {
|
|
public bool CheckValidationResult(
|
|
ServicePoint srvPoint, X509Certificate certificate,
|
|
WebRequest request, int certificateProblem) {
|
|
return true;
|
|
}
|
|
}
|
|
"@
|
|
}
|
|
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
|
|
} catch {
|
|
Write-Warning "Could not set certificate policy: $_"
|
|
}
|
|
|
|
# Also set callback for newer .NET versions (backup method)
|
|
try {
|
|
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { param($sender, $cert, $chain, $errors) return $true }
|
|
} catch {}
|
|
}
|
|
|
|
# Load System.Web for URL encoding (used in WebClient fallback)
|
|
try {
|
|
Add-Type -AssemblyName System.Web -ErrorAction SilentlyContinue
|
|
} catch {}
|
|
|
|
#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)
|
|
|
|
$fullUrl = "$ApiUrl`?action=getShopfloorPCs"
|
|
Write-Log "Querying API: $fullUrl" -Level "INFO"
|
|
|
|
# Try PowerShell native method first
|
|
try {
|
|
# Build params - use SkipCertificateCheck for PS7+, rely on ServicePointManager for PS5
|
|
$restParams = @{
|
|
Uri = $fullUrl
|
|
Method = 'Get'
|
|
ErrorAction = 'Stop'
|
|
UseBasicParsing = $true
|
|
TimeoutSec = 30
|
|
}
|
|
|
|
# Add SkipCertificateCheck for PowerShell 7+
|
|
if ($PSVersionTable.PSVersion.Major -ge 7) {
|
|
$restParams.SkipCertificateCheck = $true
|
|
}
|
|
|
|
$response = Invoke-RestMethod @restParams
|
|
|
|
if ($response.success -and $response.data) {
|
|
Write-Log "API returned $($response.count) shopfloor PCs" -Level "SUCCESS"
|
|
return $response.data
|
|
} else {
|
|
Write-Log "No shopfloor PCs returned from API" -Level "WARNING"
|
|
return @()
|
|
}
|
|
} catch {
|
|
$errMsg = $_.Exception.Message
|
|
$innerMsg = if ($_.Exception.InnerException) { $_.Exception.InnerException.Message } else { "" }
|
|
|
|
# Check if it's a TLS/connection error - try WebClient as fallback
|
|
if ($errMsg -match "underlying connection|closed|SSL|TLS|secure channel" -or $innerMsg -match "underlying connection|closed|SSL|TLS|secure channel") {
|
|
Write-Log "TLS error detected, trying WebClient fallback..." -Level "WARNING"
|
|
|
|
try {
|
|
$webClient = New-Object System.Net.WebClient
|
|
$webClient.Headers.Add("User-Agent", "PowerShell/ShopDB-Updater")
|
|
$jsonResponse = $webClient.DownloadString($fullUrl)
|
|
$response = $jsonResponse | ConvertFrom-Json
|
|
|
|
if ($response.success -and $response.data) {
|
|
Write-Log "API returned $($response.count) shopfloor PCs (via WebClient)" -Level "SUCCESS"
|
|
return $response.data
|
|
} else {
|
|
Write-Log "No shopfloor PCs returned from API" -Level "WARNING"
|
|
return @()
|
|
}
|
|
} catch {
|
|
Write-Log "WebClient fallback also failed: $($_.Exception.Message)" -Level "ERROR"
|
|
}
|
|
}
|
|
|
|
Write-Log "Failed to query API for shopfloor PCs: $errMsg" -Level "ERROR"
|
|
Write-Log " Exception Type: $($_.Exception.GetType().FullName)" -Level "ERROR"
|
|
if ($innerMsg) {
|
|
Write-Log " Inner Exception: $innerMsg" -Level "ERROR"
|
|
}
|
|
Write-Log " Tip: Try using HTTP instead of HTTPS, or check if the server certificate is valid" -Level "INFO"
|
|
return @()
|
|
}
|
|
}
|
|
|
|
function Get-HighUptimePCsFromApi {
|
|
<#
|
|
.SYNOPSIS
|
|
Queries ShopDB API to get PCs with uptime >= specified days.
|
|
#>
|
|
param(
|
|
[string]$ApiUrl,
|
|
[int]$MinUptimeDays
|
|
)
|
|
|
|
$fullUrl = "$ApiUrl`?action=getHighUptimePCs&minUptime=$MinUptimeDays"
|
|
Write-Log "Querying API for PCs with uptime >= $MinUptimeDays days: $fullUrl" -Level "INFO"
|
|
|
|
try {
|
|
$restParams = @{
|
|
Uri = $fullUrl
|
|
Method = 'Get'
|
|
ErrorAction = 'Stop'
|
|
UseBasicParsing = $true
|
|
TimeoutSec = 30
|
|
}
|
|
|
|
if ($PSVersionTable.PSVersion.Major -ge 7) {
|
|
$restParams.SkipCertificateCheck = $true
|
|
}
|
|
|
|
$response = Invoke-RestMethod @restParams
|
|
|
|
if ($response.success -and $response.data) {
|
|
Write-Log "API returned $($response.count) PCs with uptime >= $MinUptimeDays days" -Level "SUCCESS"
|
|
return $response.data
|
|
} else {
|
|
Write-Log "No high-uptime PCs returned from API" -Level "WARNING"
|
|
return @()
|
|
}
|
|
} catch {
|
|
$errMsg = $_.Exception.Message
|
|
Write-Log "Failed to query API for high-uptime PCs: $errMsg" -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) AND the shopfloor IP subnet for IP fallback
|
|
$newValue = "*.$DnsSuffix,10.134.*"
|
|
Write-Log "Adding wildcard trust for: *.$DnsSuffix and 10.134.* (IP fallback)" -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 + IP fallback (Recommended)" -ForegroundColor Yellow
|
|
Write-Host " Run as Administrator:" -ForegroundColor Gray
|
|
Write-Host ' Set-Item WSMan:\localhost\Client\TrustedHosts -Value "*.logon.ds.ge.com,10.134.*" -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.ServiceTag = $bios.SerialNumber # Same as serial for Dell
|
|
$result.Manufacturer = $computerSystem.Manufacturer
|
|
$result.Model = $computerSystem.Model
|
|
$result.LoggedInUser = $computerSystem.UserName
|
|
$result.OSVersion = $os.Caption
|
|
$result.LastBootUpTime = if ($os.LastBootUpTime) { $os.LastBootUpTime.ToString("yyyy-MM-dd HH:mm:ss") } else { $null }
|
|
$result.TotalPhysicalMemory = [Math]::Round($computerSystem.TotalPhysicalMemory / 1GB, 2)
|
|
$result.DomainRole = $computerSystem.DomainRole
|
|
$result.CurrentTimeZone = (Get-TimeZone).Id
|
|
|
|
# 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+$') {
|
|
# 10.134.*.* is always primary for shopfloor PCs
|
|
$isPrimary = ($ip -like "10.134.*")
|
|
# Secondary/equipment IPs: 192.168.*, 10.0.*, 100.* or any non-10.134 private IP
|
|
$isMachineNetwork = (
|
|
($ip -like "192.168.*") -or
|
|
($ip -like "10.0.*") -or
|
|
($ip -like "100.*") -or
|
|
(($ip -like "10.*") -and ($ip -notlike "10.134.*"))
|
|
)
|
|
$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
|
|
IsPrimary = $isPrimary
|
|
IsMachineNetwork = $isMachineNetwork
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
# Sort so 10.134.*.* (primary) comes first
|
|
$result.NetworkInterfaces = $networkInterfaces | Sort-Object -Property @{Expression={$_.IsPrimary}; Descending=$true}
|
|
|
|
# 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 'MachineNo\s*=\s*(.+)') { $dncConfig.MachineNo = $matches[1].Trim() }
|
|
if ($iniContent -match 'FtpHostPrimary\s*=\s*(.+)') { $dncConfig.FTPHostPrimary = $matches[1].Trim() }
|
|
}
|
|
$result.DNCConfig = $dncConfig
|
|
|
|
# Get machine number from registry or DNC config
|
|
$machineNo = $null
|
|
# Try registry first - GE Aircraft Engines DNC location
|
|
$regPaths = @(
|
|
"HKLM:\SOFTWARE\GE Aircraft Engines\DNC\General",
|
|
"HKLM:\SOFTWARE\WOW6432Node\GE Aircraft Engines\DNC\General"
|
|
)
|
|
foreach ($regPath in $regPaths) {
|
|
if (Test-Path $regPath) {
|
|
$machineNo = (Get-ItemProperty -Path $regPath -Name "MachineNo" -ErrorAction SilentlyContinue).MachineNo
|
|
if ($machineNo) { break }
|
|
}
|
|
}
|
|
# Fall back to DNC config
|
|
if (-not $machineNo -and $dncConfig.MachineNo) {
|
|
$machineNo = $dncConfig.MachineNo
|
|
}
|
|
|
|
# Check if machine number is generic (don't send to API)
|
|
$genericTypeHint = $null
|
|
if ($machineNo) {
|
|
$genericMachineTypes = @{
|
|
"^WJPRT" = "Measuring" # Generic printer/measuring tool
|
|
"^WJCMM" = "CMM" # Generic CMM
|
|
"^WJMEAS" = "Measuring" # Generic measuring
|
|
"^0600$" = "Wax Trace" # Wax trace machines
|
|
"^0612$" = "Inspection" # Part markers
|
|
"^0613$" = "Inspection" # Part markers
|
|
"^0615" = "Inspection" # Part markers
|
|
"^8003$" = "Inspection" # Part markers
|
|
"^TEST" = $null # Test machines
|
|
"^TEMP" = $null # Temporary
|
|
"^DEFAULT"= $null # Default value
|
|
"^0+$" = $null # All zeros
|
|
}
|
|
|
|
foreach ($pattern in $genericMachineTypes.Keys) {
|
|
if ($machineNo -match $pattern) {
|
|
$genericTypeHint = $genericMachineTypes[$pattern]
|
|
$result.IsGenericMachineNo = $true
|
|
$result.GenericTypeHint = $genericTypeHint
|
|
$machineNo = $null # Don't send generic machine numbers
|
|
break
|
|
}
|
|
}
|
|
}
|
|
$result.MachineNo = $machineNo
|
|
|
|
# Get GE Aircraft Engines registry info (DualPath configuration)
|
|
$geInfo = @{
|
|
Registry32Bit = $false
|
|
Registry64Bit = $false
|
|
DualPathEnabled = $null
|
|
Path1Name = $null
|
|
Path2Name = $null
|
|
}
|
|
$gePaths = @{
|
|
'32bit' = 'HKLM:\SOFTWARE\GE Aircraft Engines'
|
|
'64bit' = 'HKLM:\SOFTWARE\WOW6432Node\GE Aircraft Engines'
|
|
}
|
|
foreach ($pathType in $gePaths.Keys) {
|
|
$basePath = $gePaths[$pathType]
|
|
if (Test-Path $basePath) {
|
|
if ($pathType -eq '32bit') { $geInfo.Registry32Bit = $true }
|
|
else { $geInfo.Registry64Bit = $true }
|
|
|
|
$efocasPath = "$basePath\DNC\eFocas"
|
|
if (Test-Path $efocasPath) {
|
|
$efocasValues = Get-ItemProperty -Path $efocasPath -ErrorAction SilentlyContinue
|
|
if ($efocasValues.DualPath -and $geInfo.DualPathEnabled -eq $null) {
|
|
$geInfo.DualPathEnabled = ($efocasValues.DualPath -eq 'YES')
|
|
}
|
|
if (-not $geInfo.Path1Name -and $efocasValues.Path1Name) {
|
|
$geInfo.Path1Name = $efocasValues.Path1Name
|
|
}
|
|
if (-not $geInfo.Path2Name -and $efocasValues.Path2Name) {
|
|
$geInfo.Path2Name = $efocasValues.Path2Name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$result.GERegistryInfo = $geInfo
|
|
|
|
# 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\*",
|
|
"HKCU:\SOFTWARE\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
|
|
|
|
# Get default printer FQDN (network printers only)
|
|
$defaultPrinterFQDN = $null
|
|
try {
|
|
$defaultPrinter = Get-WmiObject -Query "SELECT * FROM Win32_Printer WHERE Default=$true" -ErrorAction SilentlyContinue
|
|
if ($defaultPrinter -and $defaultPrinter.PortName) {
|
|
$portName = $defaultPrinter.PortName
|
|
# Skip local/virtual printers
|
|
$localPorts = @('USB', 'LPT', 'COM', 'PORTPROMPT:', 'FILE:', 'NUL:', 'XPS', 'PDF', 'FOXIT', 'Microsoft')
|
|
$isLocalPrinter = $false
|
|
foreach ($localPort in $localPorts) {
|
|
if ($portName -like "$localPort*") { $isLocalPrinter = $true; break }
|
|
}
|
|
if (-not $isLocalPrinter) {
|
|
# Strip anything after underscore (e.g., 10.80.92.53_2 -> 10.80.92.53)
|
|
$defaultPrinterFQDN = $portName -replace '_.*$', ''
|
|
}
|
|
}
|
|
} catch { }
|
|
$result.DefaultPrinterFQDN = $defaultPrinterFQDN
|
|
|
|
# ================================================================
|
|
# Detect installed applications for PC type classification
|
|
# ================================================================
|
|
|
|
# Get all installed apps once for efficiency (with version info)
|
|
$installedApps = @()
|
|
$installedAppsWithVersion = @()
|
|
|
|
# Check machine-wide registry paths
|
|
foreach ($path in $regPaths) {
|
|
if (Test-Path $path) {
|
|
$apps = Get-ItemProperty $path -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.DisplayName -and $_.DisplayName.Trim() -ne "" }
|
|
foreach ($app in $apps) {
|
|
$installedApps += $app.DisplayName
|
|
$installedAppsWithVersion += @{
|
|
DisplayName = $app.DisplayName.Trim()
|
|
Version = if ($app.DisplayVersion) { $app.DisplayVersion.Trim() } else { "" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Check per-user installed apps by enumerating loaded hives in HKU
|
|
$hkuKeys = Get-ChildItem "Registry::HKU" -ErrorAction SilentlyContinue
|
|
foreach ($key in $hkuKeys) {
|
|
$sid = $key.PSChildName
|
|
# Only check real user SIDs (S-1-5-21-*), skip _Classes entries
|
|
if ($sid -match "^S-1-5-21-" -and $sid -notmatch "_Classes$") {
|
|
$userUninstallPath = "Registry::HKU\$sid\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*"
|
|
if (Test-Path $userUninstallPath -ErrorAction SilentlyContinue) {
|
|
$apps = Get-ItemProperty $userUninstallPath -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.DisplayName -and $_.DisplayName.Trim() -ne "" }
|
|
foreach ($app in $apps) {
|
|
if ($app.DisplayName -notin $installedApps) {
|
|
$installedApps += $app.DisplayName
|
|
$installedAppsWithVersion += @{
|
|
DisplayName = $app.DisplayName.Trim()
|
|
Version = if ($app.DisplayVersion) { $app.DisplayVersion.Trim() } else { "" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# ================================================================
|
|
# Match against tracked applications (embedded from applications.csv)
|
|
# ================================================================
|
|
$trackedAppPatterns = @(
|
|
@{ app_id = 2; app_name = "UDC"; patterns = @("Universal Data Collection") }
|
|
@{ app_id = 4; app_name = "CLM"; patterns = @("PPDCS", "CLM") }
|
|
@{ app_id = 6; app_name = "PC-DMIS"; patterns = @("PC-DMIS", "PCDMIS") }
|
|
@{ app_id = 7; app_name = "Oracle"; patterns = @("OracleDatabase", "Oracle Database", "Oracle.*Database") }
|
|
@{ app_id = 8; app_name = "eDNC"; patterns = @("eDNC") }
|
|
@{ app_id = 22; app_name = "OpenText"; patterns = @("OpenText", "CSF") }
|
|
@{ app_id = 30; app_name = "Tanium"; patterns = @("^Tanium Client") }
|
|
@{ app_id = 76; app_name = "FormTracePak"; patterns = @("FormTracePak", "Formtracepak", "Form Trace", "FormTrace") }
|
|
@{ app_id = 69; app_name = "Keyence VR Series"; patterns = @("VR-3000", "VR-5000", "VR-6000", "KEYENCE VR") }
|
|
@{ app_id = 70; app_name = "Genspect"; patterns = @("Genspect") }
|
|
@{ app_id = 71; app_name = "GageCal"; patterns = @("GageCal") }
|
|
@{ app_id = 72; app_name = "NI Software"; patterns = @("^NI-", "National Instruments", "NI System", "NI Measurement", "NI LabVIEW") }
|
|
@{ app_id = 73; app_name = "goCMM"; patterns = @("goCMM") }
|
|
@{ app_id = 74; app_name = "DODA"; patterns = @("Dovetail Digital Analysis", "DODA") }
|
|
@{ app_id = 75; app_name = "FormStatusMonitor"; patterns = @("FormStatusMonitor") }
|
|
@{ app_id = 77; app_name = "HeatTreat"; patterns = @("HeatTreat") }
|
|
@{ app_id = 82; app_name = "GE Aerospace Dashboard"; patterns = @("^GE Aerospace Dashboard") }
|
|
@{ app_id = 83; app_name = "GE Aerospace Lobby Display"; patterns = @("^GE Aerospace Lobby Display") }
|
|
)
|
|
|
|
$matchedApps = @()
|
|
foreach ($tracked in $trackedAppPatterns) {
|
|
foreach ($installedApp in $installedAppsWithVersion) {
|
|
$matched = $false
|
|
foreach ($pattern in $tracked.patterns) {
|
|
if ($installedApp.DisplayName -match $pattern) {
|
|
$matched = $true
|
|
break
|
|
}
|
|
}
|
|
if ($matched) {
|
|
# Avoid duplicates
|
|
if (-not ($matchedApps | Where-Object { $_.appid -eq $tracked.app_id })) {
|
|
$matchedApps += @{
|
|
appid = $tracked.app_id
|
|
appname = $tracked.app_name
|
|
version = $installedApp.Version
|
|
displayname = $installedApp.DisplayName
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
# ================================================================
|
|
# Detect running processes for UDC and CLM to set isactive
|
|
# ================================================================
|
|
$udcRunning = $false
|
|
$clmRunning = $false
|
|
|
|
# Check for UDC process
|
|
$udcProcess = Get-Process -Name "UDC" -ErrorAction SilentlyContinue
|
|
if ($udcProcess) { $udcRunning = $true }
|
|
|
|
# Check for CLM process (PPMon.exe)
|
|
$clmProcess = Get-Process -Name "PPMon" -ErrorAction SilentlyContinue
|
|
if ($clmProcess) { $clmRunning = $true }
|
|
|
|
# Update matched apps with isactive status
|
|
foreach ($app in $matchedApps) {
|
|
if ($app.appid -eq 2) {
|
|
# UDC
|
|
$app.isactive = if ($udcRunning) { 1 } else { 0 }
|
|
} elseif ($app.appid -eq 4) {
|
|
# CLM
|
|
$app.isactive = if ($clmRunning) { 1 } else { 0 }
|
|
} else {
|
|
# Other apps - default to active if installed
|
|
$app.isactive = 1
|
|
}
|
|
}
|
|
|
|
$result.UDCRunning = $udcRunning
|
|
$result.CLMRunning = $clmRunning
|
|
|
|
# Store matched apps as JSON string to survive WinRM serialization
|
|
$result.MatchedAppsCount = $matchedApps.Count
|
|
$result.MatchedAppNames = ($matchedApps | ForEach-Object { $_.appname }) -join ", "
|
|
$result.AllInstalledApps = ($installedApps | Sort-Object) -join "|"
|
|
$result.AllInstalledAppsCount = $installedApps.Count
|
|
if ($matchedApps.Count -gt 0) {
|
|
$result.MatchedAppsJson = ($matchedApps | ConvertTo-Json -Compress)
|
|
} else {
|
|
$result.MatchedAppsJson = ""
|
|
}
|
|
|
|
# CMM Detection: PC-DMIS, goCMM, DODA
|
|
$hasPcDmis = $false
|
|
$hasGoCMM = $false
|
|
$hasDODA = $false
|
|
foreach ($app in $installedApps) {
|
|
if ($app -match "PC-DMIS|PCDMIS") { $hasPcDmis = $true }
|
|
if ($app -match "^goCMM") { $hasGoCMM = $true }
|
|
if ($app -match "Dovetail Digital Analysis|DODA") { $hasDODA = $true }
|
|
}
|
|
# 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
|
|
}
|
|
}
|
|
}
|
|
$result.HasPcDmis = $hasPcDmis
|
|
$result.HasGoCMM = $hasGoCMM
|
|
$result.HasDODA = $hasDODA
|
|
$result.IsCMM = ($hasPcDmis -or $hasGoCMM -or $hasDODA)
|
|
|
|
# Wax Trace Detection: FormTracePak, FormStatusMonitor
|
|
$hasFormTracePak = $false
|
|
$hasFormStatusMonitor = $false
|
|
foreach ($app in $installedApps) {
|
|
if ($app -match "FormTracePak|Formtracepak|Form Trace|FormTrace") { $hasFormTracePak = $true }
|
|
if ($app -match "FormStatusMonitor") { $hasFormStatusMonitor = $true }
|
|
}
|
|
# Check file path fallback
|
|
if (-not $hasFormTracePak) {
|
|
$ftPaths = @("C:\Program Files\MitutoyoApp*", "C:\Program Files (x86)\MitutoyoApp*")
|
|
foreach ($ftPath in $ftPaths) {
|
|
if (Test-Path $ftPath) { $hasFormTracePak = $true; break }
|
|
}
|
|
}
|
|
$result.HasFormTracePak = $hasFormTracePak
|
|
$result.HasFormStatusMonitor = $hasFormStatusMonitor
|
|
$result.IsWaxTrace = ($hasFormTracePak -or $hasFormStatusMonitor)
|
|
|
|
# Keyence Detection: VR Series, Keyence VR USB Driver
|
|
$hasKeyence = $false
|
|
foreach ($app in $installedApps) {
|
|
if ($app -match "VR-3000|VR-5000|VR-6000|KEYENCE VR") {
|
|
$hasKeyence = $true
|
|
break
|
|
}
|
|
}
|
|
$result.HasKeyence = $hasKeyence
|
|
$result.IsKeyence = $hasKeyence
|
|
|
|
# EAS1000 Detection: GageCal, NI Software (National Instruments)
|
|
$hasGageCal = $false
|
|
$hasNISoftware = $false
|
|
foreach ($app in $installedApps) {
|
|
if ($app -match "^GageCal") { $hasGageCal = $true }
|
|
if ($app -match "^NI-|National Instruments|NI System|NI Measurement|NI LabVIEW") { $hasNISoftware = $true }
|
|
}
|
|
$result.HasGageCal = $hasGageCal
|
|
$result.HasNISoftware = $hasNISoftware
|
|
$result.IsEAS1000 = ($hasGageCal -or $hasNISoftware)
|
|
|
|
# Genspect Detection
|
|
$hasGenspect = $false
|
|
foreach ($app in $installedApps) {
|
|
if ($app -match "^Genspect") {
|
|
$hasGenspect = $true
|
|
break
|
|
}
|
|
}
|
|
$result.HasGenspect = $hasGenspect
|
|
|
|
# Heat Treat Detection
|
|
$hasHeatTreat = $false
|
|
foreach ($app in $installedApps) {
|
|
if ($app -match "^HeatTreat") {
|
|
$hasHeatTreat = $true
|
|
break
|
|
}
|
|
}
|
|
$result.HasHeatTreat = $hasHeatTreat
|
|
|
|
# Dashboard Detection (GE Aerospace Dashboard kiosk installer)
|
|
$hasDashboard = $false
|
|
foreach ($app in $installedApps) {
|
|
if ($app -match "^GE Aerospace Dashboard") {
|
|
$hasDashboard = $true
|
|
break
|
|
}
|
|
}
|
|
$result.HasDashboard = $hasDashboard
|
|
$result.IsDashboard = $hasDashboard
|
|
|
|
# Lobby Display Detection (GE Aerospace Lobby Display kiosk installer)
|
|
$hasLobbyDisplay = $false
|
|
foreach ($app in $installedApps) {
|
|
if ($app -match "^GE Aerospace Lobby Display") {
|
|
$hasLobbyDisplay = $true
|
|
break
|
|
}
|
|
}
|
|
$result.HasLobbyDisplay = $hasLobbyDisplay
|
|
$result.IsLobbyDisplay = $hasLobbyDisplay
|
|
|
|
# Determine PC Type based on detected software
|
|
# Priority: Dashboard > Lobby Display > CMM > Wax Trace > Keyence > EAS1000 > Genspect > Heat Treat > Generic hint > Shopfloor (default)
|
|
$detectedPcType = "Shopfloor" # Default for shopfloor PCs
|
|
if ($hasDashboard) {
|
|
$detectedPcType = "Dashboard"
|
|
} elseif ($hasLobbyDisplay) {
|
|
$detectedPcType = "Lobby Display"
|
|
} elseif ($result.IsCMM) {
|
|
$detectedPcType = "CMM"
|
|
} elseif ($result.IsWaxTrace) {
|
|
$detectedPcType = "Wax Trace"
|
|
} elseif ($result.IsKeyence) {
|
|
$detectedPcType = "Keyence"
|
|
} elseif ($result.IsEAS1000) {
|
|
$detectedPcType = "EAS1000"
|
|
} elseif ($hasGenspect) {
|
|
$detectedPcType = "Genspect"
|
|
} elseif ($hasHeatTreat) {
|
|
$detectedPcType = "Heat Treat"
|
|
} elseif ($result.GenericTypeHint) {
|
|
# Use generic machine number hint when no software detected
|
|
$detectedPcType = $result.GenericTypeHint
|
|
}
|
|
$result.DetectedPcType = $detectedPcType
|
|
|
|
$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 - use detected type from software analysis, fallback to machine number
|
|
$pcType = "Measuring" # Default
|
|
if ($PCData.DetectedPcType) {
|
|
$pcType = $PCData.DetectedPcType
|
|
} elseif ($PCData.MachineNo) {
|
|
$pcType = "Shopfloor"
|
|
}
|
|
|
|
# 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 last boot time if available
|
|
if ($PCData.LastBootUpTime) {
|
|
$postData.lastBootUpTime = $PCData.LastBootUpTime
|
|
}
|
|
|
|
# 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 new parity fields
|
|
if ($PCData.ServiceTag) {
|
|
$postData.serviceTag = $PCData.ServiceTag
|
|
}
|
|
if ($PCData.TotalPhysicalMemory) {
|
|
$postData.totalPhysicalMemory = $PCData.TotalPhysicalMemory
|
|
}
|
|
if ($PCData.DomainRole -ne $null) {
|
|
$postData.domainRole = $PCData.DomainRole
|
|
}
|
|
if ($PCData.CurrentTimeZone) {
|
|
$postData.currentTimeZone = $PCData.CurrentTimeZone
|
|
}
|
|
|
|
# Add GE Registry info (DualPath configuration)
|
|
if ($PCData.GERegistryInfo) {
|
|
$postData.dncGeRegistry32Bit = if ($PCData.GERegistryInfo.Registry32Bit) { "1" } else { "0" }
|
|
$postData.dncGeRegistry64Bit = if ($PCData.GERegistryInfo.Registry64Bit) { "1" } else { "0" }
|
|
if ($PCData.GERegistryInfo.DualPathEnabled -ne $null) {
|
|
$postData.dncDualPathEnabled = if ($PCData.GERegistryInfo.DualPathEnabled) { "1" } else { "0" }
|
|
}
|
|
if ($PCData.GERegistryInfo.Path1Name) {
|
|
$postData.dncPath1Name = $PCData.GERegistryInfo.Path1Name
|
|
}
|
|
if ($PCData.GERegistryInfo.Path2Name) {
|
|
$postData.dncPath2Name = $PCData.GERegistryInfo.Path2Name
|
|
}
|
|
}
|
|
|
|
# Add default printer FQDN
|
|
if ($PCData.DefaultPrinterFQDN) {
|
|
$postData.defaultPrinterFQDN = $PCData.DefaultPrinterFQDN
|
|
}
|
|
|
|
# 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)
|
|
}
|
|
|
|
# Add matched/tracked applications (already JSON from remote scriptblock)
|
|
if ($PCData.MatchedAppsJson -and $PCData.MatchedAppsJson -ne "") {
|
|
$postData.installedApps = $PCData.MatchedAppsJson
|
|
}
|
|
|
|
# Build params for API call
|
|
$restParams = @{
|
|
Uri = $ApiUrl
|
|
Method = 'Post'
|
|
Body = $postData
|
|
ErrorAction = 'Stop'
|
|
UseBasicParsing = $true
|
|
TimeoutSec = 30
|
|
}
|
|
|
|
# Add SkipCertificateCheck for PowerShell 7+
|
|
if ($PSVersionTable.PSVersion.Major -ge 7) {
|
|
$restParams.SkipCertificateCheck = $true
|
|
}
|
|
|
|
# Send to API
|
|
$response = Invoke-RestMethod @restParams
|
|
|
|
# Debug: log full API response for relationship troubleshooting
|
|
Write-Log " API Response: $($response | ConvertTo-Json -Compress)" -Level "INFO"
|
|
|
|
return @{
|
|
Success = $response.success
|
|
Message = $response.message
|
|
MachineId = $response.machineid
|
|
RelationshipCreated = $response.relationshipCreated
|
|
}
|
|
} catch {
|
|
$errMsg = $_.Exception.Message
|
|
$innerMsg = if ($_.Exception.InnerException) { $_.Exception.InnerException.Message } else { "" }
|
|
|
|
# Try WebClient fallback for TLS errors
|
|
if ($errMsg -match "underlying connection|closed|SSL|TLS|secure channel" -or $innerMsg -match "underlying connection|closed|SSL|TLS|secure channel") {
|
|
try {
|
|
$webClient = New-Object System.Net.WebClient
|
|
$webClient.Headers.Add("Content-Type", "application/x-www-form-urlencoded")
|
|
$webClient.Headers.Add("User-Agent", "PowerShell/ShopDB-Updater")
|
|
|
|
# Convert postData hashtable to form-urlencoded string
|
|
$formData = ($postData.GetEnumerator() | ForEach-Object {
|
|
"$([System.Web.HttpUtility]::UrlEncode($_.Key))=$([System.Web.HttpUtility]::UrlEncode($_.Value))"
|
|
}) -join "&"
|
|
|
|
$jsonResponse = $webClient.UploadString($ApiUrl, $formData)
|
|
$response = $jsonResponse | ConvertFrom-Json
|
|
|
|
return @{
|
|
Success = $response.success
|
|
Message = $response.message
|
|
MachineId = $response.machineid
|
|
RelationshipCreated = $response.relationshipCreated
|
|
}
|
|
} catch {
|
|
# WebClient also failed
|
|
}
|
|
}
|
|
|
|
return @{
|
|
Success = $false
|
|
Message = if ($innerMsg) { "$errMsg ($innerMsg)" } else { $errMsg }
|
|
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
|
|
}
|
|
|
|
# Handle Reboot mode
|
|
if ($Reboot) {
|
|
Write-Log "=== Reboot Mode ===" -Level "INFO"
|
|
Write-Log "Minimum Uptime: $MinUptimeDays days" -Level "INFO"
|
|
Write-Log "API URL: $ApiUrl" -Level "INFO"
|
|
|
|
# Query API for high-uptime PCs
|
|
$highUptimePCs = Get-HighUptimePCsFromApi -ApiUrl $ApiUrl -MinUptimeDays $MinUptimeDays
|
|
|
|
if ($highUptimePCs.Count -eq 0) {
|
|
Write-Log "No PCs found with uptime >= $MinUptimeDays days" -Level "WARNING"
|
|
exit 0
|
|
}
|
|
|
|
# Display PCs to be rebooted
|
|
Write-Host ""
|
|
Write-Host "=" * 80 -ForegroundColor Red
|
|
Write-Host " WARNING: The following $($highUptimePCs.Count) PCs will be REBOOTED" -ForegroundColor Red
|
|
Write-Host "=" * 80 -ForegroundColor Red
|
|
Write-Host ""
|
|
Write-Host (" {0,-25} {1,-15} {2,-12} {3,-20}" -f "Hostname", "Machine #", "Uptime Days", "Last Boot") -ForegroundColor Yellow
|
|
Write-Host " $("-" * 75)" -ForegroundColor Gray
|
|
|
|
foreach ($pc in $highUptimePCs) {
|
|
$machineNo = if ($pc.machinenumber) { $pc.machinenumber } else { "-" }
|
|
$uptimeDays = if ($pc.uptime_days) { $pc.uptime_days } else { "?" }
|
|
$lastBoot = if ($pc.lastboottime) { $pc.lastboottime } else { "Unknown" }
|
|
Write-Host (" {0,-25} {1,-15} {2,-12} {3,-20}" -f $pc.hostname, $machineNo, $uptimeDays, $lastBoot)
|
|
}
|
|
|
|
Write-Host " $("-" * 75)" -ForegroundColor Gray
|
|
Write-Host ""
|
|
|
|
if ($WhatIf) {
|
|
Write-Log "WhatIf mode - no reboots will be performed" -Level "WARNING"
|
|
Write-Host " Would reboot $($highUptimePCs.Count) PCs" -ForegroundColor Yellow
|
|
exit 0
|
|
}
|
|
|
|
# Confirmation
|
|
if (-not $Force) {
|
|
Write-Host " Are you sure you want to reboot these $($highUptimePCs.Count) PCs?" -ForegroundColor Red
|
|
Write-Host ""
|
|
$confirm = Read-Host " Type 'YES' to confirm"
|
|
if ($confirm -ne "YES") {
|
|
Write-Log "Reboot cancelled by user" -Level "WARNING"
|
|
exit 0
|
|
}
|
|
Write-Host ""
|
|
}
|
|
|
|
# Prompt for credentials if not provided
|
|
if (-not $Credential) {
|
|
Write-Log "No credentials provided. Prompting for credentials..." -Level "INFO"
|
|
$Credential = Get-Credential -Message "Enter admin credentials for remote PCs"
|
|
if (-not $Credential) {
|
|
Write-Log "Credentials required. Exiting." -Level "ERROR"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
Write-Log "Starting reboot process..." -Level "INFO"
|
|
|
|
# Build FQDN list
|
|
$targetPCs = @()
|
|
foreach ($pc in $highUptimePCs) {
|
|
$fqdn = if ($pc.hostname -like "*.*") { $pc.hostname } else { "$($pc.hostname).$DnsSuffix" }
|
|
$targetPCs += @{
|
|
Hostname = $pc.hostname
|
|
FQDN = $fqdn
|
|
UptimeDays = $pc.uptime_days
|
|
}
|
|
}
|
|
|
|
# Reboot each PC
|
|
$rebootSuccess = 0
|
|
$rebootFailed = 0
|
|
|
|
foreach ($target in $targetPCs) {
|
|
Write-Log "Rebooting $($target.Hostname) (uptime: $($target.UptimeDays) days)..." -Level "INFO"
|
|
|
|
try {
|
|
$rebootParams = @{
|
|
ComputerName = $target.FQDN
|
|
Force = $true
|
|
ErrorAction = 'Stop'
|
|
}
|
|
if ($Credential) {
|
|
$rebootParams.Credential = $Credential
|
|
}
|
|
|
|
Restart-Computer @rebootParams
|
|
Write-Log " -> Reboot command sent successfully" -Level "SUCCESS"
|
|
$rebootSuccess++
|
|
} catch {
|
|
Write-Log " -> FAILED: $($_.Exception.Message)" -Level "ERROR"
|
|
$rebootFailed++
|
|
}
|
|
}
|
|
|
|
# Summary
|
|
Write-Host ""
|
|
Write-Host "=" * 60 -ForegroundColor Cyan
|
|
Write-Host " REBOOT SUMMARY" -ForegroundColor Cyan
|
|
Write-Host "=" * 60 -ForegroundColor Cyan
|
|
Write-Host ""
|
|
Write-Host " Total PCs: $($targetPCs.Count)" -ForegroundColor White
|
|
Write-Host " Successful: $rebootSuccess" -ForegroundColor Green
|
|
Write-Host " Failed: $rebootFailed" -ForegroundColor $(if ($rebootFailed -gt 0) { "Red" } else { "White" })
|
|
Write-Host ""
|
|
Write-Host "=" * 60 -ForegroundColor Cyan
|
|
|
|
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)
|
|
# Timeouts: 20s to connect, 120s for operations (app detection takes time)
|
|
$sessionOption = New-PSSessionOption -OpenTimeout 20000 -OperationTimeout 120000 -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.LoggedInUser) {
|
|
Write-Log " Logged In: $($result.LoggedInUser)" -Level "INFO"
|
|
} else {
|
|
Write-Log " Logged In: (none)" -Level "INFO"
|
|
}
|
|
|
|
if ($result.MachineNo) {
|
|
Write-Log " Machine #: $($result.MachineNo)" -Level "INFO"
|
|
} elseif ($result.IsGenericMachineNo) {
|
|
Write-Log " Machine #: (generic - requires manual assignment)" -Level "WARNING"
|
|
}
|
|
|
|
# Show detected PC type and software
|
|
if ($result.DetectedPcType) {
|
|
$detectedSoftware = @()
|
|
if ($result.HasDashboard) { $detectedSoftware += "GE Aerospace Dashboard" }
|
|
if ($result.HasLobbyDisplay) { $detectedSoftware += "GE Aerospace Lobby Display" }
|
|
if ($result.HasPcDmis) { $detectedSoftware += "PC-DMIS" }
|
|
if ($result.HasGoCMM) { $detectedSoftware += "goCMM" }
|
|
if ($result.HasDODA) { $detectedSoftware += "DODA" }
|
|
if ($result.HasFormTracePak) { $detectedSoftware += "FormTracePak" }
|
|
if ($result.HasFormStatusMonitor) { $detectedSoftware += "FormStatusMonitor" }
|
|
if ($result.HasKeyence) { $detectedSoftware += "Keyence VR" }
|
|
if ($result.HasGageCal) { $detectedSoftware += "GageCal" }
|
|
if ($result.HasNISoftware) { $detectedSoftware += "NI Software" }
|
|
if ($result.HasGenspect) { $detectedSoftware += "Genspect" }
|
|
if ($result.HasHeatTreat) { $detectedSoftware += "HeatTreat" }
|
|
|
|
$softwareStr = if ($detectedSoftware.Count -gt 0) { " (" + ($detectedSoftware -join ", ") + ")" } else { "" }
|
|
Write-Log " PC Type: $($result.DetectedPcType)$softwareStr" -Level "INFO"
|
|
}
|
|
|
|
# Log tracked apps count
|
|
if ($result.MatchedAppsCount -and $result.MatchedAppsCount -gt 0) {
|
|
Write-Log " Tracked Apps: $($result.MatchedAppsCount) matched ($($result.MatchedAppNames))" -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"
|
|
if ($apiResult.RelationshipCreated) {
|
|
Write-Log " -> Machine Relationship: Created" -Level "SUCCESS"
|
|
} elseif ($result.MachineNo) {
|
|
Write-Log " -> Machine Relationship: FAILED (machineNo=$($result.MachineNo))" -Level "ERROR"
|
|
}
|
|
|
|
# Update WinRM status - since we successfully connected via WinRM, mark it as enabled
|
|
try {
|
|
$winrmBody = @{
|
|
action = "updateWinRMStatus"
|
|
hostname = $result.Hostname
|
|
hasWinRM = "1"
|
|
}
|
|
$winrmParams = @{
|
|
Uri = $ApiUrl
|
|
Method = 'Post'
|
|
Body = $winrmBody
|
|
ErrorAction = 'Stop'
|
|
UseBasicParsing = $true
|
|
TimeoutSec = 15
|
|
}
|
|
if ($PSVersionTable.PSVersion.Major -ge 7) {
|
|
$winrmParams.SkipCertificateCheck = $true
|
|
}
|
|
$winrmResponse = Invoke-RestMethod @winrmParams
|
|
if ($winrmResponse.success) {
|
|
Write-Log " -> WinRM status updated" -Level "SUCCESS"
|
|
}
|
|
} catch {
|
|
Write-Log " -> WinRM status update failed (non-critical): $_" -Level "WARNING"
|
|
}
|
|
|
|
$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 ""
|
|
}
|
|
|
|
# Collect connection errors for IP fallback retry
|
|
$connectionFailures = @()
|
|
foreach ($err in $remoteErrors) {
|
|
$targetPC = if ($err.TargetObject) { $err.TargetObject } else { "Unknown" }
|
|
Write-Log "[FAIL] ${targetPC}: $($err.Exception.Message)" -Level "ERROR"
|
|
$connectionFailures += @{
|
|
Hostname = $targetPC
|
|
FQDN = $targetPC
|
|
Error = $err.Exception.Message
|
|
}
|
|
}
|
|
|
|
# IP Fallback: Retry failed connections using recorded IP addresses (10.134.*.*)
|
|
if ($connectionFailures.Count -gt 0) {
|
|
Write-Host ""
|
|
Write-Log "Attempting IP fallback for $($connectionFailures.Count) failed connection(s)..." -Level "INFO"
|
|
|
|
foreach ($failure in $connectionFailures) {
|
|
# Extract hostname from FQDN
|
|
$hostname = $failure.FQDN -replace "\..*$", ""
|
|
|
|
# Query API for recorded IP address
|
|
try {
|
|
$ipLookupBody = @{
|
|
action = "getRecordedIP"
|
|
hostname = $hostname
|
|
}
|
|
$ipLookupParams = @{
|
|
Uri = $ApiUrl
|
|
Method = 'Post'
|
|
Body = $ipLookupBody
|
|
ErrorAction = 'Stop'
|
|
UseBasicParsing = $true
|
|
TimeoutSec = 15
|
|
}
|
|
if ($PSVersionTable.PSVersion.Major -ge 7) {
|
|
$ipLookupParams.SkipCertificateCheck = $true
|
|
}
|
|
$ipResponse = Invoke-RestMethod @ipLookupParams
|
|
|
|
if ($ipResponse.success -and $ipResponse.ipaddress -and $ipResponse.ipaddress -match "^10\.134\.") {
|
|
$fallbackIP = $ipResponse.ipaddress
|
|
Write-Log " Found recorded IP for ${hostname}: $fallbackIP - retrying..." -Level "INFO"
|
|
|
|
# Retry connection using IP address
|
|
$ipSessionParams = @{
|
|
ComputerName = $fallbackIP
|
|
ScriptBlock = (Get-RemotePCInfo)
|
|
SessionOption = $sessionOption
|
|
Authentication = 'Negotiate'
|
|
ErrorAction = 'Stop'
|
|
}
|
|
if ($Credential) {
|
|
$ipSessionParams.Credential = $Credential
|
|
}
|
|
|
|
try {
|
|
$ipResult = Invoke-Command @ipSessionParams
|
|
|
|
if ($ipResult.Success) {
|
|
Write-Log "[OK] $($ipResult.Hostname) (via IP: $fallbackIP)" -Level "SUCCESS"
|
|
Write-Log " Serial: $($ipResult.SerialNumber) | Model: $($ipResult.Model) | OS: $($ipResult.OSVersion)" -Level "INFO"
|
|
|
|
if ($ipResult.NetworkInterfaces -and $ipResult.NetworkInterfaces.Count -gt 0) {
|
|
$ips = ($ipResult.NetworkInterfaces | ForEach-Object { $_.IPAddress }) -join ", "
|
|
Write-Log " IPs: $ips" -Level "INFO"
|
|
}
|
|
|
|
if ($ipResult.DetectedPcType) {
|
|
Write-Log " PC Type: $($ipResult.DetectedPcType)" -Level "INFO"
|
|
}
|
|
|
|
if ($ipResult.MatchedAppsCount -and $ipResult.MatchedAppsCount -gt 0) {
|
|
Write-Log " Tracked Apps: $($ipResult.MatchedAppsCount) matched ($($ipResult.MatchedAppNames))" -Level "INFO"
|
|
}
|
|
|
|
# Send to API
|
|
Write-Log " Sending to API..." -Level "INFO"
|
|
$apiResult = Send-PCDataToApi -PCData $ipResult -ApiUrl $ApiUrl
|
|
|
|
if ($apiResult.Success) {
|
|
Write-Log " -> Updated in ShopDB (MachineID: $($apiResult.MachineId))" -Level "SUCCESS"
|
|
if ($apiResult.RelationshipCreated) {
|
|
Write-Log " -> Machine Relationship: Created" -Level "SUCCESS"
|
|
} elseif ($ipResult.MachineNo) {
|
|
Write-Log " -> Machine Relationship: FAILED (machineNo=$($ipResult.MachineNo))" -Level "ERROR"
|
|
}
|
|
$successCount++
|
|
$successPCs += @{
|
|
Hostname = $ipResult.Hostname
|
|
MachineId = $apiResult.MachineId
|
|
Serial = $ipResult.SerialNumber
|
|
ViaIP = $fallbackIP
|
|
}
|
|
} else {
|
|
Write-Log " -> API Error: $($apiResult.Message)" -Level "ERROR"
|
|
$failCount++
|
|
$failedPCs += @{ Hostname = $hostname; Error = "API: $($apiResult.Message)" }
|
|
}
|
|
} else {
|
|
Write-Log "[FAIL] $hostname (via IP: $fallbackIP): $($ipResult.Error)" -Level "ERROR"
|
|
$failCount++
|
|
$failedPCs += @{ Hostname = $hostname; Error = $ipResult.Error }
|
|
}
|
|
} catch {
|
|
Write-Log "[FAIL] $hostname (via IP: $fallbackIP): $($_.Exception.Message)" -Level "ERROR"
|
|
$failCount++
|
|
$failedPCs += @{ Hostname = $hostname; Error = $_.Exception.Message }
|
|
}
|
|
} else {
|
|
# No valid IP found, add to failed list
|
|
Write-Log " No 10.134.*.* IP recorded for $hostname - skipping fallback" -Level "WARNING"
|
|
$failCount++
|
|
$failedPCs += @{ Hostname = $hostname; Error = $failure.Error }
|
|
}
|
|
} catch {
|
|
Write-Log " Failed to lookup IP for ${hostname}: $($_.Exception.Message)" -Level "WARNING"
|
|
$failCount++
|
|
$failedPCs += @{ Hostname = $hostname; Error = $failure.Error }
|
|
}
|
|
Write-Host ""
|
|
}
|
|
}
|
|
|
|
# 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
|