# PowerGrid QuickBooks Server Discovery # Run on each of the 7 servers (TS01-04, file host/DC, analytics, utilities) # Read-only. Outputs JSON + a summary zip you can copy back to your workstation. # # Usage on the server (from elevated PowerShell): # .\qb-server-discovery.ps1 # Or, if execution policy blocks it: # powershell -ExecutionPolicy Bypass -File .\qb-server-discovery.ps1 $ErrorActionPreference = 'Continue' $ProgressPreference = 'SilentlyContinue' # =========================================================================== # STEP 1: ADMIN PRIVILEGE CHECK # =========================================================================== Write-Host "================================================" -ForegroundColor Cyan Write-Host " PowerGrid QB Migration - Server Discovery" -ForegroundColor Cyan Write-Host "================================================" -ForegroundColor Cyan Write-Host "" $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal($currentUser) $isAdmin = $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) Write-Host "Running as : $($currentUser.Name)" Write-Host "Admin? : $isAdmin" if (-not $isAdmin) { Write-Host "" Write-Host "ERROR: Script is not running with administrator privileges." -ForegroundColor Red Write-Host "Re-launch PowerShell with 'Run as administrator' and try again." -ForegroundColor Red Write-Host "" Write-Host "Continuing in DEGRADED MODE - many sections will fail." -ForegroundColor Yellow } # Live-test a few admin-only operations to confirm the token actually works Write-Host "" Write-Host "Probing admin-only operations:" $probes = @( @{ Name = 'Read HKLM\SYSTEM'; Test = { Get-Item 'HKLM:\SYSTEM' -ErrorAction Stop | Out-Null } }, @{ Name = 'Enumerate local users'; Test = { Get-LocalUser -ErrorAction Stop | Out-Null } }, @{ Name = 'Query Win32_Service'; Test = { Get-CimInstance Win32_Service -ErrorAction Stop | Select-Object -First 1 | Out-Null } }, @{ Name = 'Read C:\Windows\System32';Test = { Get-ChildItem 'C:\Windows\System32' -ErrorAction Stop | Select-Object -First 1 | Out-Null } }, @{ Name = 'Read SMB shares'; Test = { Get-SmbShare -ErrorAction Stop | Out-Null } }, @{ Name = 'Read firewall rules'; Test = { Get-NetFirewallRule -ErrorAction Stop | Select-Object -First 1 | Out-Null } } ) foreach ($p in $probes) { try { & $p.Test; Write-Host (" [OK ] {0}" -f $p.Name) -ForegroundColor Green } catch { Write-Host (" [FAIL] {0} - {1}" -f $p.Name, $_.Exception.Message) -ForegroundColor Red } } Write-Host "" # =========================================================================== # STEP 2: PREPARE OUTPUT # =========================================================================== $hostname = $env:COMPUTERNAME $stamp = Get-Date -Format 'yyyyMMdd-HHmmss' $outRoot = "C:\Discovery" $outDir = Join-Path $outRoot "$hostname-$stamp" New-Item -ItemType Directory -Force -Path $outDir | Out-Null Write-Host "Writing output to: $outDir" -ForegroundColor Yellow Write-Host "" function Save-Json { param([string]$Name, $Data) $path = Join-Path $outDir "$Name.json" try { $Data | ConvertTo-Json -Depth 6 -WarningAction SilentlyContinue | Out-File -Encoding utf8 $path $size = (Get-Item $path).Length Write-Host (" [OK ] {0,-30} {1,8} bytes" -f $Name, $size) -ForegroundColor Green } catch { Write-Host (" [FAIL] {0,-30} {1}" -f $Name, $_.Exception.Message) -ForegroundColor Red } } function Try-Collect { param([string]$Name, [scriptblock]$Script) try { $data = & $Script Save-Json -Name $Name -Data $data } catch { Save-Json -Name $Name -Data @{ error = $_.Exception.Message } } } # =========================================================================== # STEP 3: COLLECT DATA # =========================================================================== Write-Host "=== System identity ===" -ForegroundColor Cyan Try-Collect 'system-info' { $cs = Get-CimInstance Win32_ComputerSystem $os = Get-CimInstance Win32_OperatingSystem $bios = Get-CimInstance Win32_BIOS @{ Hostname = $env:COMPUTERNAME FQDN = ([System.Net.Dns]::GetHostByName($env:COMPUTERNAME)).HostName Domain = $cs.Domain PartOfDomain = $cs.PartOfDomain Workgroup = $cs.Workgroup DomainRole = $cs.DomainRole # 0=Standalone WS, 1=Member WS, 2=Standalone Srv, 3=Member Srv, 4=BDC, 5=PDC Manufacturer = $cs.Manufacturer Model = $cs.Model SystemType = $cs.SystemType BiosSerial = $bios.SerialNumber OSName = $os.Caption OSVersion = $os.Version OSBuild = $os.BuildNumber InstallDate = $os.InstallDate LastBoot = $os.LastBootUpTime Architecture = $os.OSArchitecture TimeZone = (Get-TimeZone).Id UptimeHours = [math]::Round(((Get-Date) - $os.LastBootUpTime).TotalHours, 1) } } Write-Host "" Write-Host "=== Hardware ===" -ForegroundColor Cyan Try-Collect 'hardware' { $cpu = Get-CimInstance Win32_Processor $mem = Get-CimInstance Win32_PhysicalMemory @{ Sockets = ($cpu | Measure-Object).Count CPUModel = ($cpu | Select-Object -First 1).Name Cores = ($cpu | Measure-Object NumberOfCores -Sum).Sum LogicalCPUs = ($cpu | Measure-Object NumberOfLogicalProcessors -Sum).Sum TotalMemoryGB = [math]::Round((($mem | Measure-Object Capacity -Sum).Sum / 1GB), 1) MemorySticks = $mem | ForEach-Object { @{ CapacityGB=[math]::Round($_.Capacity/1GB,1); Speed=$_.Speed; Manufacturer=$_.Manufacturer } } } } Try-Collect 'disks' { Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3" | ForEach-Object { @{ Drive = $_.DeviceID Label = $_.VolumeName FileSystem = $_.FileSystem SizeGB = [math]::Round($_.Size/1GB, 1) FreeGB = [math]::Round($_.FreeSpace/1GB, 1) UsedGB = [math]::Round(($_.Size - $_.FreeSpace)/1GB, 1) PercentFree = if ($_.Size) { [math]::Round(($_.FreeSpace/$_.Size)*100, 1) } else { 0 } } } } Try-Collect 'physical-disks' { Get-PhysicalDisk -ErrorAction SilentlyContinue | Select-Object FriendlyName, MediaType, Size, BusType, SerialNumber, HealthStatus } Write-Host "" Write-Host "=== Networking ===" -ForegroundColor Cyan Try-Collect 'network-adapters' { Get-NetAdapter | Where-Object Status -eq 'Up' | ForEach-Object { $cfg = Get-NetIPConfiguration -InterfaceIndex $_.ifIndex @{ Name = $_.Name Description = $_.InterfaceDescription MAC = $_.MacAddress LinkSpeed = $_.LinkSpeed IPv4Addresses = $cfg.IPv4Address | ForEach-Object { @{ IP=$_.IPAddress; Prefix=$_.PrefixLength } } IPv4Gateway = $cfg.IPv4DefaultGateway.NextHop DNSServers = $cfg.DNSServer.ServerAddresses DHCPEnabled = (Get-NetIPInterface -InterfaceIndex $_.ifIndex -AddressFamily IPv4).Dhcp -eq 'Enabled' } } } Try-Collect 'routes' { Get-NetRoute -AddressFamily IPv4 | Where-Object { $_.DestinationPrefix -notlike '127.*' -and $_.DestinationPrefix -notlike '224.*' -and $_.DestinationPrefix -notlike '255.*' } | Select-Object DestinationPrefix, NextHop, RouteMetric, InterfaceAlias | Sort-Object RouteMetric, DestinationPrefix } Try-Collect 'listening-ports' { Get-NetTCPConnection -State Listen | ForEach-Object { $proc = Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue @{ LocalAddress = $_.LocalAddress LocalPort = $_.LocalPort ProcessId = $_.OwningProcess ProcessName = $proc.ProcessName ProcessPath = $proc.Path } } | Sort-Object LocalPort } Try-Collect 'hosts-file' { if (Test-Path 'C:\Windows\System32\drivers\etc\hosts') { Get-Content 'C:\Windows\System32\drivers\etc\hosts' | Where-Object { $_ -and $_ -notmatch '^\s*#' -and $_.Trim() } } } Try-Collect 'connectivity-probes' { @{ # Test connectivity to PowerGrid's existing Azure SQL (the failed ERP / analytics dependency) 'pgserp.database.windows.net' = (Test-NetConnection -ComputerName 'pgserp.database.windows.net' -Port 1433 -InformationLevel Quiet -WarningAction SilentlyContinue) # Generic internet reachability sanity 'azure.microsoft.com:443' = (Test-NetConnection -ComputerName 'azure.microsoft.com' -Port 443 -InformationLevel Quiet -WarningAction SilentlyContinue) # DNS lookup of the existing QB DNS record 'quickbooks.powergridservices.com' = try { (Resolve-DnsName -Name 'quickbooks.powergridservices.com' -Type A -ErrorAction Stop).IPAddress } catch { $_.Exception.Message } } } Write-Host "" Write-Host "=== Domain / AD ===" -ForegroundColor Cyan Try-Collect 'domain-info' { $cs = Get-CimInstance Win32_ComputerSystem $info = @{ Domain = $cs.Domain PartOfDomain = $cs.PartOfDomain DomainRole = $cs.DomainRole } # If this server is a DC, capture more if ($cs.DomainRole -in 4,5) { try { $info.IsDomainController = $true if (Get-Command Get-ADDomain -ErrorAction SilentlyContinue) { $dom = Get-ADDomain $info.DomainDN = $dom.DistinguishedName $info.NetBIOS = $dom.NetBIOSName $info.Forest = $dom.Forest $info.DomainMode= $dom.DomainMode.ToString() $info.FSMORoles = @{ PDC = $dom.PDCEmulator RIDMaster = $dom.RIDMaster InfrastructureMaster = $dom.InfrastructureMaster } $forest = Get-ADForest $info.FSMORoles.SchemaMaster = $forest.SchemaMaster $info.FSMORoles.DomainNamingMaster= $forest.DomainNamingMaster $info.DCs = (Get-ADDomainController -Filter *).Name } } catch { $info.ADError = $_.Exception.Message } } $info } Write-Host "" Write-Host "=== Local accounts and groups ===" -ForegroundColor Cyan Try-Collect 'local-users' { Get-LocalUser | Select-Object Name, Enabled, LastLogon, PasswordRequired, PasswordChangeableDate, PasswordExpires, PasswordLastSet, Description, FullName } Try-Collect 'local-admins' { Get-LocalGroupMember -Group 'Administrators' | Select-Object Name, ObjectClass, PrincipalSource } Try-Collect 'local-groups' { Get-LocalGroup | Select-Object Name, Description } Write-Host "" Write-Host "=== Services ===" -ForegroundColor Cyan Try-Collect 'services' { Get-CimInstance Win32_Service | ForEach-Object { @{ Name = $_.Name DisplayName = $_.DisplayName StartMode = $_.StartMode State = $_.State StartName = $_.StartName PathName = $_.PathName } } | Sort-Object Name } Write-Host "" Write-Host "=== Installed software ===" -ForegroundColor Cyan Try-Collect 'installed-software' { $paths = @( 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' ) Get-ItemProperty $paths -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName } | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate, InstallLocation | Sort-Object DisplayName -Unique } Try-Collect 'windows-features' { if (Get-Command Get-WindowsFeature -ErrorAction SilentlyContinue) { Get-WindowsFeature | Where-Object Installed | Select-Object Name, DisplayName, FeatureType } } Try-Collect 'hotfixes' { Get-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 50 HotFixID, Description, InstalledOn, InstalledBy } Write-Host "" Write-Host "=== Shares and DFS ===" -ForegroundColor Cyan Try-Collect 'smb-shares' { Get-SmbShare | Where-Object { $_.Name -notmatch '^\$' -or $_.Name -in @('ADMIN$','C$','IPC$') } | ForEach-Object { $access = try { Get-SmbShareAccess -Name $_.Name | Select-Object AccountName, AccessControlType, AccessRight } catch { $null } @{ Name = $_.Name Path = $_.Path Description = $_.Description ShareType = $_.ShareType ScopeName = $_.ScopeName Access = $access } } } Try-Collect 'mapped-drives' { Get-CimInstance Win32_MappedLogicalDisk -ErrorAction SilentlyContinue | Select-Object Name, ProviderName, SessionID, VolumeName } Write-Host "" Write-Host "=== Scheduled tasks (non-Microsoft) ===" -ForegroundColor Cyan Try-Collect 'scheduled-tasks' { Get-ScheduledTask | Where-Object { $_.TaskPath -notlike '\Microsoft\*' -and $_.TaskPath -ne '\' } | ForEach-Object { $info = Get-ScheduledTaskInfo -TaskName $_.TaskName -TaskPath $_.TaskPath -ErrorAction SilentlyContinue @{ TaskName = $_.TaskName TaskPath = $_.TaskPath State = $_.State.ToString() Author = $_.Author Description = $_.Description LastRun = $info.LastRunTime NextRun = $info.NextRunTime Actions = $_.Actions | ForEach-Object { @{ Execute=$_.Execute; Arguments=$_.Arguments; WorkingDirectory=$_.WorkingDirectory } } } } } Write-Host "" Write-Host "=== Firewall ===" -ForegroundColor Cyan Try-Collect 'firewall-profile' { Get-NetFirewallProfile | Select-Object Name, Enabled, DefaultInboundAction, DefaultOutboundAction } Try-Collect 'firewall-rules-inbound-allow' { Get-NetFirewallRule -Direction Inbound -Action Allow -Enabled True | Where-Object { $_.Group -notlike '@*' } | ForEach-Object { $port = $_ | Get-NetFirewallPortFilter -ErrorAction SilentlyContinue @{ Name = $_.DisplayName Profile = $_.Profile.ToString() LocalPort = $port.LocalPort Protocol = $port.Protocol Description = $_.Description } } } Write-Host "" Write-Host "=== Printers ===" -ForegroundColor Cyan Try-Collect 'printers' { Get-Printer -ErrorAction SilentlyContinue | Select-Object Name, DriverName, PortName, Shared, ShareName, Type } Write-Host "" Write-Host "=== Application-specific detection ===" -ForegroundColor Cyan Try-Collect 'app-quickbooks' { $qb = @{} $qb.RegistryKeys = Get-ChildItem 'HKLM:\SOFTWARE\Intuit' -Recurse -ErrorAction SilentlyContinue | Select-Object Name, Property -First 50 $qb.WOWRegistryKeys = Get-ChildItem 'HKLM:\SOFTWARE\Wow6432Node\Intuit' -Recurse -ErrorAction SilentlyContinue | Select-Object Name, Property -First 50 $qb.InstalledVersions = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -like 'QuickBooks*' } | Select-Object DisplayName, DisplayVersion, InstallLocation, InstallDate $qb.DatabaseServiceStatus = Get-Service -Name 'QuickBooksDB*','QBCFMonitorService' -ErrorAction SilentlyContinue | Select-Object Name, Status, StartType $qb } Try-Collect 'app-tsplus' { $tsp = @{} $tsp.RegistryPresent = Test-Path 'HKLM:\SOFTWARE\Digital River\Terminal Service Plus' if ($tsp.RegistryPresent) { $tsp.Settings = Get-ItemProperty 'HKLM:\SOFTWARE\Digital River\Terminal Service Plus' -ErrorAction SilentlyContinue } $tsp.InstallDir = @('C:\Program Files (x86)\TSplus','C:\Program Files\TSplus') | Where-Object { Test-Path $_ } if ($tsp.InstallDir) { $tsp.Files = Get-ChildItem $tsp.InstallDir[0] -Filter '*.exe' -ErrorAction SilentlyContinue | Select-Object Name, @{N='Version';E={$_.VersionInfo.FileVersion}} } $tsp.Services = Get-Service | Where-Object { $_.Name -match 'TSplus|XtraAdmin|AdminTool' } | Select-Object Name, Status, StartType $tsp } Try-Collect 'app-easy-credit' { $ec = @{} $ec.LikelyPaths = @( 'C:\Program Files\Easy Credit', 'C:\Program Files (x86)\Easy Credit', 'C:\EasyCredit', 'C:\Program Files\EZCredit', 'C:\Program Files (x86)\EZCredit' ) | Where-Object { Test-Path $_ } $ec.RegistryMatches = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -match 'Easy Credit|EZCredit|EZ-Credit' } | Select-Object DisplayName, DisplayVersion, InstallLocation, Publisher $ec.RelatedServices = Get-Service | Where-Object { $_.Name -match 'Credit' -or $_.DisplayName -match 'Credit' } | Select-Object Name, DisplayName, Status, StartType $ec } Try-Collect 'app-qcube-analytics' { # Q-Cube extract / Access DBs / Azure SQL connections on analytics or utilities server $a = @{} $a.QCubeFolders = Get-ChildItem 'C:\','D:\','U:\','Q:\' -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -match 'Q[-\s]?Cube|QCube|FPNA|Analytics' } | Select-Object FullName, LastWriteTime $a.AccessFiles = Get-ChildItem 'C:\','D:\','U:\','Q:\' -Include '*.mdb','*.accdb' -Recurse -ErrorAction SilentlyContinue -Depth 4 | Select-Object FullName, Length, LastWriteTime -First 50 $a.ODBCDSNs = Get-OdbcDsn -ErrorAction SilentlyContinue | Select-Object Name, DsnType, DriverName, Attribute $a.ODBCDrivers = Get-OdbcDriver -ErrorAction SilentlyContinue | Select-Object Name, Platform, Attribute $a } Write-Host "" Write-Host "=== PowerShell + .NET versions ===" -ForegroundColor Cyan Try-Collect 'runtime-versions' { @{ PowerShellVersion = $PSVersionTable.PSVersion.ToString() DotNetVersions = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -Recurse -ErrorAction SilentlyContinue | Get-ItemProperty -Name Version -ErrorAction SilentlyContinue | Where-Object { $_.PSChildName -match '^(?!S)\p{L}' } | Select-Object PSChildName, Version -Unique } } # =========================================================================== # STEP 4: SUMMARIZE + ZIP # =========================================================================== Write-Host "" Write-Host "================================================" -ForegroundColor Cyan Write-Host " Summary" -ForegroundColor Cyan Write-Host "================================================" -ForegroundColor Cyan $files = Get-ChildItem $outDir -Filter *.json Write-Host ("Files written: {0}" -f $files.Count) Write-Host ("Total size : {0:N0} bytes" -f (($files | Measure-Object Length -Sum).Sum)) Write-Host "" $zipPath = "$outDir.zip" try { Compress-Archive -Path "$outDir\*" -DestinationPath $zipPath -Force Write-Host "Zipped to: $zipPath" -ForegroundColor Green } catch { Write-Host "Zip failed: $($_.Exception.Message)" -ForegroundColor Red } Write-Host "" Write-Host "Done." -ForegroundColor Green Write-Host $zipPath -ForegroundColor Green