Read-only re-check on the shared R1.05 test server plus a diff of the R1.03 and R1.05 manual editions. - installation-security: R1.05 OS list, Defender exceptions, IT security 2.0/3.0, LAPS, automatic configuration merge (jupgutl), PRM chapter, port 34597; setup_check.ps1 gets -Version (default 1.05.00). - data-access: L45 renumbered (new ch. 4 RESTful API), extended OPC UA functions off by default, OPC UA methods (OPCUAC_METHOD_CALL2_DF), Information Model Mapper, ServerConfig.xml in %TLS_SAV_PATH%. - equipment: Modbus unchanged; IEC 104/61850 run as EQT managers (eqtdbg, eqtfrcscn), IEC 61850 report control blocks, upgrade steps. - Issues ISS-076..090; R1.05 notes on ISS-003/008/032/034/061; progress and version-differences updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
168 lines
12 KiB
PowerShell
168 lines
12 KiB
PowerShell
# CI Server R1.05 / R1.03 - read-only setup check
|
|
# Compares a machine with the Windows settings required by CI Server Setup (IM 36K01C10-01EN §1.3.2, §2.1.2)
|
|
# and the IT security state described in the Security Guide (IM 36K01C30-01EN §2.2, §3.2, §3.5.4).
|
|
# R1.05 rules: 5th editions (Feb. 2026); R1.03 rules: 3rd editions (Sep. 2023).
|
|
# Changes nothing. Run in Windows PowerShell 5.1; some checks need an elevated session.
|
|
# Usage: powershell -NoProfile -ExecutionPolicy Bypass -File tools\setup_check.ps1 [-Version 1.05.00|1.03.00]
|
|
param(
|
|
[ValidateSet('1.05.00', '1.03.00')]
|
|
[string]$Version = '1.05.00'
|
|
)
|
|
$isR103 = ($Version -eq '1.03.00')
|
|
"Target: CI Server R$Version"
|
|
|
|
$results = New-Object System.Collections.Generic.List[object]
|
|
function Add-Result($area, $check, $expected, $actual, $ok) {
|
|
$status = 'CHECK'
|
|
if ($ok -eq $true) { $status = 'OK' } elseif ($ok -eq $false) { $status = 'DIFF' }
|
|
$results.Add([pscustomobject]@{ Status = $status; Area = $area; Check = $check; Expected = $expected; Actual = "$actual" })
|
|
}
|
|
function Get-Reg($path, $name) {
|
|
try { (Get-ItemProperty -Path $path -Name $name -ErrorAction Stop).$name } catch { $null }
|
|
}
|
|
|
|
# --- CI Server ---------------------------------------------------------------
|
|
$ci = $null
|
|
foreach ($k in 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*') {
|
|
foreach ($i in (Get-ItemProperty $k -ErrorAction SilentlyContinue)) { if ($i.DisplayName -like 'CI Server R*') { $ci = $i } }
|
|
}
|
|
if ($ci) { Add-Result 'CI Server' 'Installed version' $Version $ci.DisplayVersion ($ci.DisplayVersion -eq $Version) }
|
|
else { Add-Result 'CI Server' 'Installed version' $Version 'not found' $false }
|
|
$svc = Get-Service StartCIServer -ErrorAction SilentlyContinue
|
|
if ($svc) {
|
|
Add-Result 'CI Server' 'CI Service state' 'Running (when in use)' $svc.Status $null
|
|
Add-Result 'CI Server' 'CI Service startup type' 'Automatic in operation; Manual during upgrade' $svc.StartType $null
|
|
$acct = (Get-CimInstance Win32_Service -Filter "Name='StartCIServer'" -ErrorAction SilentlyContinue).StartName
|
|
Add-Result 'CI Server' 'CI Service account' '.\CIX_PROCESS (default)' $acct ($acct -like '*CIX_PROCESS')
|
|
}
|
|
$vagw = Get-Service | Where-Object { $_.DisplayName -like 'VAGW*' }
|
|
Add-Result 'CI Server' 'VAGW Bossd service' 'present only if installed with IT Security Tool' ($(if ($vagw) { ($vagw | ForEach-Object { "$($_.DisplayName)=$($_.Status)" }) -join ', ' } else { 'not installed' })) $null
|
|
|
|
# --- Operating system --------------------------------------------------------
|
|
$os = Get-CimInstance Win32_OperatingSystem
|
|
$osText = "$($os.Caption) build $($os.BuildNumber)"
|
|
if ($isR103) {
|
|
# R1.03 (IM 36K01C10-01EN 3rd ed. §1)
|
|
$supported = @('Windows 10 Pro', 'Windows 10 Enterprise', 'Windows 10 IoT Enterprise', 'Windows Server 2016', 'Windows Server 2019', 'Windows Server IoT 2019')
|
|
$osOk = $false; foreach ($s in $supported) { if ($os.Caption -like "*$s*") { $osOk = $true } }
|
|
Add-Result 'Windows' 'Operating system' 'Win10 Pro/Enterprise/IoT LTSB 2016/LTSC 2019, Server 2016/2019' $osText $osOk
|
|
if ($os.Caption -like '*Windows 10 Pro*') { Add-Result 'Windows' 'Windows 10 Pro note' 'Vnet/IP drivers and IT Security Tool not supported on Win10 Pro' 'Windows 10 Pro' $null }
|
|
}
|
|
else {
|
|
# R1.05 (IM 36K01C10-01EN 5th ed. §1.3.2): Server editions are listed as Standard only
|
|
$supported = @(
|
|
'Windows 10 (IoT )?Enterprise LTSC (2019|2021)',
|
|
'Windows 11 (Pro|Enterprise)\b',
|
|
'Windows 11 IoT Enterprise LTSC 2024',
|
|
'Windows Server (IoT )?(2019|2022|2025) Standard'
|
|
)
|
|
$osOk = $false; foreach ($s in $supported) { if ($os.Caption -match $s) { $osOk = $true } }
|
|
Add-Result 'Windows' 'Operating system' 'Win10 (IoT) Ent. LTSC 2019/2021, Win11 Pro/Ent., Win11 (IoT) Ent. LTSC 2024, Server (IoT) 2019/2022/2025 Standard' $osText $osOk
|
|
$noItSec = ($os.Caption -match 'Windows Server (IoT )?2025') -or (($os.Caption -match 'Windows 11') -and ($os.Caption -notmatch 'LTSC'))
|
|
if ($noItSec) { Add-Result 'Windows' 'Vnet/IP and IT security note' 'Vnet/IP drivers and IT Security Tool not supported on Win11 Pro/Ent. and Server 2025' $os.Caption $null }
|
|
}
|
|
|
|
# Visual effects: "Let Windows choose" = VisualFXSetting 0
|
|
$vfx = Get-Reg 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\VisualEffects' 'VisualFXSetting'
|
|
Add-Result 'Windows' 'Visual effects' "Let Windows choose (0)" $(if ($null -eq $vfx) { 'not set (default = Let Windows choose)' } else { $vfx }) ($null -eq $vfx -or $vfx -eq 0)
|
|
|
|
# Virtual memory: custom size = 1.5 x RAM, not automatic
|
|
$ramMB = [math]::Round((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1MB)
|
|
$auto = (Get-CimInstance Win32_ComputerSystem).AutomaticManagedPagefile
|
|
$pf = Get-CimInstance Win32_PageFileSetting -ErrorAction SilentlyContinue
|
|
$pfText = if ($pf) { ($pf | ForEach-Object { "$($_.Name) $($_.InitialSize)-$($_.MaximumSize) MB" }) -join '; ' } else { 'system managed' }
|
|
$want = [math]::Round($ramMB * 1.5)
|
|
$pfOk = (-not $auto) -and $pf -and (($pf | Where-Object { $_.InitialSize -ge $want * 0.95 -and $_.MaximumSize -ge $want * 0.95 }) -ne $null)
|
|
Add-Result 'Windows' 'Virtual memory' "custom, initial = max = ~$want MB (1.5 x $ramMB MB RAM)" "automatic=$auto; $pfText" $pfOk
|
|
|
|
# Processor scheduling: Programs = Win32PrioritySeparation 0x26 (38) or 2
|
|
$prio = Get-Reg 'HKLM:\SYSTEM\CurrentControlSet\Control\PriorityControl' 'Win32PrioritySeparation'
|
|
Add-Result 'Windows' 'Processor scheduling' 'Programs (38 or 2)' $prio ($prio -eq 38 -or $prio -eq 2)
|
|
|
|
# Power plan
|
|
$plan = (powercfg /getactivescheme) 2>$null
|
|
Add-Result 'Windows' 'Power plan' 'High performance; never sleep/hibernate, disk and display never off' $plan ($plan -match 'High performance')
|
|
|
|
# Fast startup
|
|
$hib = Get-Reg 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Power' 'HiberbootEnabled'
|
|
Add-Result 'Windows' 'Fast startup (HiberbootEnabled)' '0' $hib ($hib -eq 0)
|
|
|
|
# Windows Defender
|
|
$defPol = Get-Reg 'HKLM:\SOFTWARE\Policies\Microsoft\Windows Defender' 'DisableAntiSpyware'
|
|
$defSvc = Get-Service WinDefend -ErrorAction SilentlyContinue
|
|
$defText = "policy DisableAntiSpyware=$defPol; service=$(if ($defSvc) { $defSvc.Status } else { 'none' })"
|
|
# R1.05 exceptions (IM 36K01C10-01EN 5th ed. §2.1.2, IM 36K01C30-01EN 5th ed. §3.5.4):
|
|
# Win11 Pro 24H2 (build 26100+) must keep Defender on; Win10 LTSC 2021 (build 19044) and Win11 LTSC 2024 cannot turn it off.
|
|
$defKeepOn = (-not $isR103) -and ($os.Caption -match 'Windows 11 Pro') -and ([int]$os.BuildNumber -ge 26100)
|
|
$defCannotOff = (-not $isR103) -and (($os.Caption -match 'Windows 11 .*LTSC') -or (($os.Caption -match 'Windows 10 .*LTSC') -and ([int]$os.BuildNumber -eq 19044)))
|
|
if ($defKeepOn) { Add-Result 'Windows' 'Windows Defender' 'Win11 Pro 24H2: must stay on' $defText $null }
|
|
elseif ($defCannotOff) { Add-Result 'Windows' 'Windows Defender' 'Win10 LTSC 2021 / Win11 LTSC 2024: cannot be turned off' $defText $null }
|
|
else { Add-Result 'Windows' 'Windows Defender' 'turned off by policy (use Yokogawa-approved antivirus)' $defText ($defPol -eq 1) }
|
|
|
|
# Automatic Windows updates
|
|
$au = Get-Reg 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' 'NoAutoUpdate'
|
|
Add-Result 'Windows' 'Automatic Windows updates' 'Disabled by policy (NoAutoUpdate=1)' $au ($au -eq 1)
|
|
|
|
# Scheduled drive optimization and component cleanup
|
|
foreach ($t in @(@('\Microsoft\Windows\Defrag\', 'ScheduledDefrag', 'Automatic drive optimization'), @('\Microsoft\Windows\Servicing\', 'StartComponentCleanup', 'Automatic cleanup'))) {
|
|
$task = Get-ScheduledTask -TaskPath $t[0] -TaskName $t[1] -ErrorAction SilentlyContinue
|
|
Add-Result 'Windows' $t[2] 'Disabled' $(if ($task) { $task.State } else { 'task not found' }) ($task -and $task.State -eq 'Disabled')
|
|
}
|
|
|
|
# .NET Framework 3.5
|
|
$net35 = Get-Reg 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5' 'Install'
|
|
Add-Result 'Windows' '.NET Framework 3.5' 'installed' $(if ($net35 -eq 1) { 'installed' } else { 'not installed' }) ($net35 -eq 1)
|
|
|
|
# DCOM default authentication level: Connect = 2
|
|
$dcom = Get-Reg 'HKLM:\SOFTWARE\Microsoft\Ole' 'LegacyAuthenticationLevel'
|
|
Add-Result 'Windows' 'DCOM Default Authentication Level' 'Connect (2)' $(if ($null -eq $dcom) { 'not set (default = Connect)' } else { $dcom }) ($null -eq $dcom -or $dcom -eq 2)
|
|
|
|
# Display scale 100 %
|
|
$dpi = Get-Reg 'HKCU:\Control Panel\Desktop\WindowMetrics' 'AppliedDPI'
|
|
Add-Result 'Windows' 'Display scale' '100 % (96 DPI)' $(if ($null -eq $dpi) { 'unknown' } else { "$dpi DPI" }) ($dpi -eq 96)
|
|
|
|
# 8dot3 file naming
|
|
$d83 = (fsutil behavior query disable8dot3) 2>$null | Select-Object -First 1
|
|
Add-Result 'Windows' '8dot3 file naming' 'state 2 (per volume) or 0 (enabled)' $d83 ($d83 -match 'is:\s*[02]\b|\b[02] \(')
|
|
|
|
# Regional settings (must not change after IT security is applied)
|
|
Add-Result 'Windows' 'Regional format / UI language' 'set before applying IT security; do not change afterwards' "$((Get-Culture).Name) / $((Get-UICulture).Name)" $null
|
|
|
|
# --- IT security (Security Guide) --------------------------------------------
|
|
$groups = 'CIX_OPERATOR', 'CIX_ENGINEER', 'CIX_MAINTENANCE', 'CIX_OPC'
|
|
foreach ($g in $groups) {
|
|
$lg = Get-LocalGroup -Name $g -ErrorAction SilentlyContinue
|
|
Add-Result 'IT security' "Local group $g" 'exists after IT Security Tool (standalone)' $(if ($lg) { 'exists' } else { 'missing' }) ([bool]$lg)
|
|
}
|
|
foreach ($u in 'CIX_PROCESS', 'CTM_PROCESS') {
|
|
$lu = Get-LocalUser -Name $u -ErrorAction SilentlyContinue
|
|
Add-Result 'IT security' "Local user $u" 'exists' $(if ($lu) { "exists, enabled=$($lu.Enabled)" } else { 'missing' }) ([bool]$lu)
|
|
}
|
|
# Program folder of the IT Security Tool; IT security 3.0 also puts its GPO file and settings PDF
|
|
# under ProgramData (IM 36K01C10-01EN 5th ed. §2.1.6)
|
|
$secPaths = @((Join-Path ${env:ProgramFiles(x86)} 'YOKOGAWA\IA\iPCS\Platform\Security'), (Join-Path $env:ProgramData 'Yokogawa\IA\iPCS\Platform\Security'))
|
|
$found = @($secPaths | Where-Object { Test-Path $_ })
|
|
$itsec = $found.Count -gt 0
|
|
$v3 = Test-Path (Join-Path $env:ProgramData 'Yokogawa\IA\iPCS\Platform\Security\GPOFiles\3.0')
|
|
$secText = if ($itsec) { "yes ($($found -join '; '))$(if ($v3) { '; IT security 3.0 files present' })" } else { 'no' }
|
|
Add-Result 'IT security' 'IT Security Tool installed' 'yes, when installed with ciserver-...-ITSecurity.exe (or the IT security 3.0 installer)' $secText $itsec
|
|
|
|
foreach ($p in (Get-NetFirewallProfile -ErrorAction SilentlyContinue)) {
|
|
Add-Result 'Firewall' "Profile $($p.Name)" 'Enabled' $p.Enabled ($p.Enabled -eq $true)
|
|
}
|
|
$ports = @{ 'TCP 8080' = 8080; 'TCP 8443' = 8443; 'UDP 17001' = 17001; 'UDP 17101' = 17101; 'UDP 18002' = 18002 }
|
|
# R1.05: RESTful API web server (jrstsrv.sup JRST_NETWORK_PORT), IM 36K01C30-01EN 5th ed. Table 3.2-1
|
|
if (-not $isR103) { $ports['TCP 34597'] = 34597 }
|
|
$allow = Get-NetFirewallRule -Direction Inbound -Action Allow -Enabled True -ErrorAction SilentlyContinue | Get-NetFirewallPortFilter -ErrorAction SilentlyContinue
|
|
foreach ($name in $ports.Keys) {
|
|
$proto, $num = $name.Split(' ')
|
|
$hit = $allow | Where-Object { $_.Protocol -eq $proto -and ($_.LocalPort -contains "$num" -or $_.LocalPort -contains 'Any') }
|
|
Add-Result 'Firewall' "Inbound $name allowed" 'yes (web / secure web / DURM / SMDMON / RESTful API)' $(if ($hit) { 'yes' } else { 'no rule found' }) ([bool]$hit)
|
|
}
|
|
|
|
# --- Output --------------------------------------------------------------------
|
|
foreach ($r in ($results | Sort-Object Area, Check)) {
|
|
"[{0,-5}] {1} / {2}: {3} (expected: {4})" -f $r.Status, $r.Area, $r.Check, $r.Actual, $r.Expected
|
|
}
|
|
$sum = $results | Group-Object Status | ForEach-Object { "$($_.Name)=$($_.Count)" }
|
|
"Summary: $($sum -join ', ') (OK = matches, DIFF = differs, CHECK = review manually)"
|