displaypcs.asp: - Replace "All Time" filter with WinRM status filter - Options: All, Needs WinRM (Has Equipment), Has WinRM, No WinRM - Add VNC IP fallback when hostname unavailable displaypc.asp: - Add VNC IP fallback when hostname unavailable api.asp: - Add isactive field support for installedapps table - UDC/CLM process detection sets isactive=1 if running, 0 if not displaymachines.asp: - Fix to exclude PC machine types (machinetypeid >= 33) - PCs were incorrectly showing on equipment list Update-ShopfloorPCs-Remote.ps1: - Add UDC.exe and PPMon.exe (CLM) process detection - Set isactive flag based on running process - Add 10.134.* to TrustedHosts for IP fallback connections Update-PC-CompleteAsset.ps1: - Add UDC/CLM process detection for local execution - Set isactive flag on tracked applications 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1195 lines
48 KiB
PowerShell
1195 lines
48 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
|
|
|
|
.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
|
|
)
|
|
|
|
# SSL/TLS Certificate Bypass for HTTPS connections
|
|
try {
|
|
if (-not ([System.Management.Automation.PSTypeName]'TrustAllCertsPolicy').Type) {
|
|
Add-Type @"
|
|
using System.Net;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
public class TrustAllCertsPolicy : ICertificatePolicy {
|
|
public bool CheckValidationResult(
|
|
ServicePoint srvPoint, X509Certificate certificate,
|
|
WebRequest request, int certificateProblem) {
|
|
return true;
|
|
}
|
|
}
|
|
"@
|
|
}
|
|
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
|
|
} catch { }
|
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
|
|
#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) 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.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 }
|
|
|
|
# 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$" = "Part Marker" # Part markers
|
|
"^0613$" = "Part Marker" # Part markers
|
|
"^0615" = "Part Marker" # Part markers
|
|
"^8003$" = "Part Marker" # 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 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
|
|
|
|
# ================================================================
|
|
# Detect installed applications for PC type classification
|
|
# ================================================================
|
|
|
|
# Get all installed apps once for efficiency (with version info)
|
|
$installedApps = @()
|
|
$installedAppsWithVersion = @()
|
|
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 { "" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# ================================================================
|
|
# 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") }
|
|
)
|
|
|
|
$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
|
|
|
|
# Determine PC Type based on detected software
|
|
# Priority: CMM > Wax Trace > Keyence > EAS1000 > Genspect > Heat Treat > Generic hint > Shopfloor (default)
|
|
$detectedPcType = "Shopfloor" # Default for shopfloor PCs
|
|
if ($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 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
|
|
}
|
|
|
|
# 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"
|
|
} elseif ($result.IsGenericMachineNo) {
|
|
Write-Log " Machine #: (generic - requires manual assignment)" -Level "WARNING"
|
|
}
|
|
|
|
# Show detected PC type and software
|
|
if ($result.DetectedPcType) {
|
|
$detectedSoftware = @()
|
|
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"
|
|
|
|
# Update WinRM status - since we successfully connected via WinRM, mark it as enabled
|
|
try {
|
|
$winrmBody = @{
|
|
action = "updateWinRMStatus"
|
|
hostname = $result.Hostname
|
|
hasWinRM = "1"
|
|
}
|
|
$winrmResponse = Invoke-RestMethod -Uri $ApiUrl -Method Post -Body $winrmBody -ErrorAction Stop
|
|
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
|
|
}
|
|
$ipResponse = Invoke-RestMethod -Uri $ApiUrl -Method Post -Body $ipLookupBody -ErrorAction Stop
|
|
|
|
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"
|
|
$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
|