diff --git a/playbook/preinstall/preinstall.json b/playbook/preinstall/preinstall.json index c9c9ba7..f2eeae4 100644 --- a/playbook/preinstall/preinstall.json +++ b/playbook/preinstall/preinstall.json @@ -71,6 +71,26 @@ "DetectionPath": "HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{F8CFEB22-A2E7-3971-9EDA-4B11EDEFC185}", "PCTypes": ["*"] }, + { + "_comment": "VC++ 2013 x64 Minimum Runtime - required by Keyence VR-6000 Series Software. Extracted from the Keyence installer's Windows Installer cache. Same REBOOT=ReallySuppress pattern as the x86 variants.", + "Name": "VC++ Redistributable 2013 x64 (Minimum)", + "Installer": "vcredist/2013-x64-min/installer.msi", + "Type": "MSI", + "InstallArgs": "/qn /norestart REBOOT=ReallySuppress NOVSUI=1 USING_EXUIH_SILENT=1", + "DetectionMethod": "Registry", + "DetectionPath": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{A749D8E6-B613-3BE3-8F5F-045C84EBA29B}", + "PCTypes": ["*"] + }, + { + "_comment": "VC++ 2013 x64 Additional Runtime - required by Keyence VR-6000 Series Software. Pairs with the Minimum Runtime above.", + "Name": "VC++ Redistributable 2013 x64 (Additional)", + "Installer": "vcredist/2013-x64-add/installer.msi", + "Type": "MSI", + "InstallArgs": "/qn /norestart REBOOT=ReallySuppress NOVSUI=1 USING_EXUIH_SILENT=1", + "DetectionMethod": "Registry", + "DetectionPath": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{929FBD26-9020-399B-9A7A-751D61F0B942}", + "PCTypes": ["*"] + }, { "_comment": "VC++ 2010 x64 - required by PC-DMIS 2016/2019 R2 on CMM PCs. PCDLRN.exe links against msvcr100.dll and the VS 2010 MFC DLLs which are only provided by this redistributable. Extracted from the PC-DMIS 2016 bundle's attached container (a1 payload). Silent install: /q /norestart. Detection: Uninstall key under the native x64 hive with fixed product GUID.", "Name": "VC++ Redistributable 2010 x64", diff --git a/playbook/shopfloor-setup/Keyence/09-Setup-Keyence.ps1 b/playbook/shopfloor-setup/Keyence/09-Setup-Keyence.ps1 index 02dd721..8c8c645 100644 --- a/playbook/shopfloor-setup/Keyence/09-Setup-Keyence.ps1 +++ b/playbook/shopfloor-setup/Keyence/09-Setup-Keyence.ps1 @@ -1,12 +1,140 @@ -# 09-Setup-Keyence.ps1 - Keyence-specific setup (runs after Shopfloor baseline) +# 09-Setup-Keyence.ps1 - Keyence type setup (runs during shopfloor-setup phase). # -# PLACEHOLDER: add type-specific app installs when details are finalized. -# This script will be called by Run-ShopfloorSetup.ps1 as part of the -# type-specific phase, after all baseline scripts have completed. +# Installs the VR-6000 Series Software MSI and the KEYENCE VR Series USB driver +# package. Both payloads ship inside this directory (shopfloor-setup\Keyence\) +# and are staged by startnet.cmd during WinPE, so they exist locally on disk +# when this script runs - no share mapping required. # -# For share-based installs, copy the pattern from CMM/09-Setup-CMM.ps1 -# (credential lookup + share mount + install from share). +# Layout (sibling to this .ps1): +# installers\VR-6000 Series Software.msi +# drivers\keyence_vr_series.inf +# drivers\KEYENCE_VR_SERIES.cat +# drivers\amd64\WdfCoInstaller01009.dll +# drivers\amd64\WinUsbCoinstaller2.dll +# +# Prereqs (installed earlier by 00-PreInstall-MachineApps.ps1 via preinstall.json): +# - Microsoft Visual C++ 2010 x86 + x64 +# - Microsoft Visual C++ 2013 x86 + x64 (Min + Add) +# - Microsoft Visual C++ 2017/2022 x86 (UCRT covers 2015-2022) +# +# Idempotent: skips the MSI if the product GUID is already registered, skips +# the driver if the package is already in the DriverStore. +# +# Log: C:\Logs\Keyence\09-Setup-Keyence.log -Write-Host "=== Keyence Setup ===" -Write-Host " (no type-specific apps configured yet)" -Write-Host "=== Keyence Setup Complete ===" +$ErrorActionPreference = 'Continue' + +$logDir = 'C:\Logs\Keyence' +$transcriptLog = Join-Path $logDir '09-Setup-Keyence.log' + +$installerDir = Join-Path $PSScriptRoot 'installers' +$msiPath = Join-Path $installerDir 'VR-6000 Series Software.msi' +$msiLog = Join-Path $logDir 'VR-6000-msiexec.log' +$driverDir = Join-Path $PSScriptRoot 'drivers' +$driverInf = Join-Path $driverDir 'keyence_vr_series.inf' + +# Product registration - ProductCode read from the MSI via msiinfo: must match +# the value baked in, so idempotency works after a successful install. +$productCode = '{058E7194-BDF8-4FA2-9D69-978BB0F25214}' +$expectedVersion = '4.3.7' +$uninstallRegPaths = @( + "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$productCode", + "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\$productCode" +) + +if (-not (Test-Path $logDir)) { + New-Item -Path $logDir -ItemType Directory -Force | Out-Null +} + +try { Start-Transcript -Path $transcriptLog -Append -Force | Out-Null } catch {} + +function Write-KeyenceLog { + param([string]$Message, [string]$Level = 'INFO') + $stamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + Write-Host "[$stamp] [$Level] $Message" +} + +Write-KeyenceLog "================================================================" +Write-KeyenceLog "=== Keyence Setup session start (PID $PID) ===" +Write-KeyenceLog "Running as: $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)" +Write-KeyenceLog "Script root: $PSScriptRoot" +Write-KeyenceLog "================================================================" + +# --- Detection: skip MSI if already installed at expected version --- +$msiAlreadyInstalled = $false +foreach ($regPath in $uninstallRegPaths) { + if (Test-Path $regPath) { + $installed = (Get-ItemProperty $regPath -ErrorAction SilentlyContinue).DisplayVersion + if ($installed -eq $expectedVersion) { + Write-KeyenceLog "MSI already installed (DisplayVersion=$installed at $regPath) - skipping" + $msiAlreadyInstalled = $true + break + } else { + Write-KeyenceLog "MSI registered but at version '$installed' (expected $expectedVersion) - will reinstall" "WARN" + } + } +} + +# --- Install MSI --- +if (-not $msiAlreadyInstalled) { + if (-not (Test-Path $msiPath)) { + Write-KeyenceLog "MSI not found at $msiPath - aborting MSI install" "ERROR" + } else { + $msiSize = [math]::Round((Get-Item $msiPath).Length / 1MB, 1) + Write-KeyenceLog "Installing VR-6000 Series Software MSI ($msiSize MB)" + $msiArgs = @( + '/i', "`"$msiPath`"", + '/qn', '/norestart', + 'ALLUSERS=1', 'REBOOT=ReallySuppress', + '/L*v', "`"$msiLog`"" + ) + $sw = [Diagnostics.Stopwatch]::StartNew() + $proc = Start-Process -FilePath 'msiexec.exe' -ArgumentList $msiArgs -Wait -PassThru -NoNewWindow + $sw.Stop() + # msiexec exit codes we tolerate: 0 = success, 3010 = success w/ reboot recommended + if ($proc.ExitCode -eq 0 -or $proc.ExitCode -eq 3010) { + Write-KeyenceLog "MSI install succeeded (exit=$($proc.ExitCode), elapsed=$([int]$sw.Elapsed.TotalSeconds)s)" + } else { + Write-KeyenceLog "MSI install FAILED (exit=$($proc.ExitCode)). See $msiLog" "ERROR" + } + } +} + +# --- Detection: skip driver if already in DriverStore --- +$driverAlreadyStaged = $false +try { + $pnpOut = & pnputil.exe /enum-drivers 2>&1 | Out-String + if ($pnpOut -match 'keyence_vr_series\.inf') { + Write-KeyenceLog "Keyence USB driver already in DriverStore - skipping" + $driverAlreadyStaged = $true + } +} catch { + Write-KeyenceLog "pnputil /enum-drivers failed ($($_.Exception.Message)); attempting install anyway" "WARN" +} + +# --- Install driver --- +if (-not $driverAlreadyStaged) { + if (-not (Test-Path $driverInf)) { + Write-KeyenceLog "Driver INF not found at $driverInf - aborting driver install" "ERROR" + } else { + Write-KeyenceLog "Adding KEYENCE VR Series USB driver to DriverStore via pnputil" + $pnp = Start-Process -FilePath 'pnputil.exe' ` + -ArgumentList '/add-driver', "`"$driverInf`"", '/install' ` + -Wait -PassThru -NoNewWindow ` + -RedirectStandardOutput (Join-Path $logDir 'pnputil-stdout.log') ` + -RedirectStandardError (Join-Path $logDir 'pnputil-stderr.log') + # pnputil exit codes: 0 = ok, 3010 = ok (reboot recommended), + # 259 = ERROR_NO_MORE_ITEMS (no INFs matched) + if ($pnp.ExitCode -eq 0 -or $pnp.ExitCode -eq 3010) { + Write-KeyenceLog "Driver install succeeded (exit=$($pnp.ExitCode))" + } else { + Write-KeyenceLog "Driver install FAILED (exit=$($pnp.ExitCode))" "ERROR" + } + } +} + +Write-KeyenceLog "================================================================" +Write-KeyenceLog "=== Keyence Setup session end ===" +Write-KeyenceLog "================================================================" + +try { Stop-Transcript | Out-Null } catch {} diff --git a/playbook/shopfloor-setup/Keyence/drivers/KEYENCE_VR_SERIES.cat b/playbook/shopfloor-setup/Keyence/drivers/KEYENCE_VR_SERIES.cat new file mode 100644 index 0000000..cbf0767 Binary files /dev/null and b/playbook/shopfloor-setup/Keyence/drivers/KEYENCE_VR_SERIES.cat differ diff --git a/playbook/shopfloor-setup/Keyence/drivers/amd64/WdfCoInstaller01009.dll b/playbook/shopfloor-setup/Keyence/drivers/amd64/WdfCoInstaller01009.dll new file mode 100644 index 0000000..47d4466 Binary files /dev/null and b/playbook/shopfloor-setup/Keyence/drivers/amd64/WdfCoInstaller01009.dll differ diff --git a/playbook/shopfloor-setup/Keyence/drivers/amd64/WinUsbCoinstaller2.dll b/playbook/shopfloor-setup/Keyence/drivers/amd64/WinUsbCoinstaller2.dll new file mode 100644 index 0000000..80506b8 Binary files /dev/null and b/playbook/shopfloor-setup/Keyence/drivers/amd64/WinUsbCoinstaller2.dll differ diff --git a/playbook/shopfloor-setup/Keyence/drivers/keyence_vr_series.inf b/playbook/shopfloor-setup/Keyence/drivers/keyence_vr_series.inf new file mode 100644 index 0000000..bea6905 --- /dev/null +++ b/playbook/shopfloor-setup/Keyence/drivers/keyence_vr_series.inf @@ -0,0 +1,94 @@ +[Version] +Signature = "$Windows NT$" +DriverPackageDisplayName=%DESC% +Class = KeyenceUSB +ClassGuid={EE304F37-F588-45FC-91FE-B76873981AD1} +Provider = %ProviderName% +CatalogFile=KEYENCE_VR_SERIES.cat +DriverVer=03/26/2020,1.0.0.0 + +; ================== Class section ================== + +[ClassInstall32] +Addreg=KeyenceClassReg + +[KeyenceClassReg] +HKR,,,0,%ClassName% +HKR,,Icon,,-5 + +; ========== Manufacturer/Models sections =========== + +[Manufacturer] +%ProviderName% = KEYENCE,NTamd64 + +[KEYENCE.NTamd64] +%USB\KeyenceDevice.DeviceDesc% =USB_Install,USB\VID_0720&PID_102C + +; =================== Installation =================== + +;[1] +[USB_Install] +Include=winusb.inf +Needs=WINUSB.NT + +;[2] +[USB_Install.Services] +Include=winusb.inf +AddService=WinUSB,0x00000002,WinUSB_ServiceInstall + +;[3] +[WinUSB_ServiceInstall] +DisplayName = %WinUSB_SvcDesc% +ServiceType = 1 +StartType = 3 +ErrorControl = 1 +ServiceBinary = %12%\WinUSB.sys + +;[4] +[USB_Install.Wdf] +KmdfService=WINUSB, WinUsb_Install + +[WinUSB_Install] +KmdfLibraryVersion=1.9 + +;[5] +[USB_Install.HW] +AddReg=Dev_AddReg + +[Dev_AddReg] +HKR,,DeviceInterfaceGUIDs,0x10000,"{B708995F-18C9-4D52-8D29-41FF2BFBF215}" + +;[6] +[USB_Install.CoInstallers] +AddReg=CoInstallers_AddReg +CopyFiles=CoInstallers_CopyFiles + +[CoInstallers_AddReg] +HKR,,CoInstallers32,0x00010000,"WdfCoInstaller01009.dll,WdfCoInstaller","WinUsbCoinstaller2.dll" + +[CoInstallers_CopyFiles] +WdfCoInstaller01009.dll +WinUsbCoinstaller2.dll + +[DestinationDirs] +CoInstallers_CopyFiles=11 + +; ================= Source Media Section ===================== +;[7] + +[SourceDisksNames] +1 = %DISK_NAME%,,,\amd64 + +[SourceDisksFiles.amd64] +WinUSBCoInstaller2.dll=1 +WdfCoInstaller01009.dll=1 + +; =================== Strings =================== + +[Strings] +ProviderName="KEYENCE" +DESC="VR Series USB-Driver" +WinUSB_SvcDesc="WinUSB.sys : KEYENCE USB Device" +DISK_NAME="KEYENCE USB Driver Installation Disk" +ClassName="KEYENCE USB Device Class" +USB\KeyenceDevice.DeviceDesc="VR Series" diff --git a/playbook/shopfloor-setup/Keyence/installers/VR-6000 Series Software.msi b/playbook/shopfloor-setup/Keyence/installers/VR-6000 Series Software.msi new file mode 100644 index 0000000..eefc02c Binary files /dev/null and b/playbook/shopfloor-setup/Keyence/installers/VR-6000 Series Software.msi differ