; Universal Printer Installer for Network Printers ; Installs HP/Xerox network printers from ShopDB via TCP/IP ; Pure Inno Setup Pascal Script - no PowerShell dependency [Setup] AppId={{7B9C3E41-5F2A-4D8B-9E1C-8A6D4F3E2B1A}} AppName=WJDT GE Aerospace Printer Installer AppVersion=2.0 AppPublisher=WJDT AppPublisherURL=http://tsgwp00524.logon.ds.ge.com AppSupportURL=http://tsgwp00524.logon.ds.ge.com AppUpdatesURL=http://tsgwp00524.logon.ds.ge.com CreateAppDir=no ChangesAssociations=no PrivilegesRequired=admin ; Force 64-bit installation mode on 64-bit compatible systems ArchitecturesInstallIn64BitMode=x64compatible OutputDir=C:\Users\570005354\Downloads\Output OutputBaseFilename=PrinterInstaller SolidCompression=yes WizardStyle=modern SetupIconFile=gea-logo.ico WizardImageFile=patrick.bmp WizardSmallImageFile=patrick-sm.bmp CreateUninstallRegKey=no DisableWelcomePage=no [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" [Messages] WelcomeLabel2=This utility will install network printers.%n%nFeatures:%n- Query HP/Xerox printers from ShopDB%n- Install via TCP/IP using FQDN or IP address%n- Automatic driver installation (HP Universal PCL6, Xerox Global Print Driver)%n%nClick Next to continue. [Files] ; HP Universal Print Driver x64 Source: "drivers\hp_x64\*"; DestDir: "{tmp}\hp_drivers_x64"; Flags: ignoreversion recursesubdirs; Check: Is64BitInstallMode ; HP Universal Print Driver x32 Source: "drivers\hp_x32\*"; DestDir: "{tmp}\hp_drivers_x32"; Flags: ignoreversion recursesubdirs; Check: not Is64BitInstallMode ; Xerox Universal Print Driver x64 Source: "drivers\xerox_x64\*"; DestDir: "{tmp}\xerox_drivers_x64"; Flags: ignoreversion recursesubdirs; Check: Is64BitInstallMode ; Xerox Universal Print Driver x32 Source: "drivers\xerox_x32\*"; DestDir: "{tmp}\xerox_drivers_x32"; Flags: ignoreversion recursesubdirs; Check: not Is64BitInstallMode ; HP DesignJet T1700dr PS3 x64 Source: "drivers\hp_designjet_x64\*"; DestDir: "{tmp}\hp_designjet_x64"; Flags: ignoreversion recursesubdirs; Check: Is64BitInstallMode ; HP DesignJet T1700dr PS3 x32 Source: "drivers\hp_designjet_x32\*"; DestDir: "{tmp}\hp_designjet_x32"; Flags: ignoreversion recursesubdirs; Check: not Is64BitInstallMode [Code] var PrinterSelectionPage: TInputOptionWizardPage; PrinterDataArray: array of record PrinterName: String; FQDN: String; Vendor: String; Model: String; IsInstalled: Boolean; end; InstallLog: TStringList; // ─── Logging ──────────────────────────────────────────────────────────────── procedure AddLog(const Msg: String); begin if InstallLog <> nil then InstallLog.Add(Msg); end; // ─── JSON helpers ──────────────────────────────────────────────────────────── // Extract a single field value from a flat JSON object string. // Returns the raw string value (unquoted), or the number/bool literal, or ''. function JsonGetString(const JsonObj, Key: String): String; var P, Q: Integer; begin Result := ''; P := Pos('"' + Key + '"', JsonObj); if P = 0 then Exit; P := P + Length('"' + Key + '"'); // Skip whitespace and colon while (P <= Length(JsonObj)) and ((JsonObj[P] = ' ') or (JsonObj[P] = ':') or (JsonObj[P] = #9)) do P := P + 1; if P > Length(JsonObj) then Exit; if JsonObj[P] = '"' then begin // Quoted string value P := P + 1; Q := P; while (Q <= Length(JsonObj)) and (JsonObj[Q] <> '"') do Q := Q + 1; Result := Copy(JsonObj, P, Q - P); end else if Copy(JsonObj, P, 4) = 'null' then Result := '' else if Copy(JsonObj, P, 4) = 'true' then Result := 'true' else if Copy(JsonObj, P, 5) = 'false' then Result := 'false' else begin // Numeric value - read until delimiter Q := P; while (Q <= Length(JsonObj)) and (JsonObj[Q] <> ',') and (JsonObj[Q] <> '}') and (JsonObj[Q] <> ' ') and (JsonObj[Q] <> #13) and (JsonObj[Q] <> #10) do Q := Q + 1; Result := Trim(Copy(JsonObj, P, Q - P)); end; end; // ─── HTTP ──────────────────────────────────────────────────────────────────── // Perform an HTTP GET using the built-in Windows WinHttp COM object. // Sends Windows credentials automatically (NTLM/Kerberos for intranet sites). function HttpGet(const Url: String; var Response: String): Boolean; var WinHttp: Variant; begin Result := False; Response := ''; try WinHttp := CreateOleObject('WinHttp.WinHttpRequest.5.1'); WinHttp.Open('GET', Url, False); WinHttp.SetAutoLogonPolicy(0); // Always send Windows credentials WinHttp.SetRequestHeader('Accept', 'application/json'); WinHttp.Send(''); if WinHttp.Status = 200 then begin Response := WinHttp.ResponseText; Result := True; end; except Result := False; end; end; // ─── Printer query ─────────────────────────────────────────────────────────── // Query ShopDB API and populate PrinterDataArray with active HP/Xerox printers. function QueryPrinters(): Boolean; var Response: String; I, Depth, ObjStart, Count: Integer; ObjStr, Name, Vendor, Model, Fqdn, Ip, Active, Address: String; begin Result := False; SetArrayLength(PrinterDataArray, 0); if not HttpGet('https://tsgwp00525.wjs.geaerospace.net/shopdb/api_printers.asp', Response) then Exit; if Response = '' then Exit; // Count top-level JSON objects to size the array Count := 0; Depth := 0; for I := 1 to Length(Response) do begin if Response[I] = '{' then begin if Depth = 0 then Count := Count + 1; Depth := Depth + 1; end else if Response[I] = '}' then Depth := Depth - 1; end; if Count = 0 then Exit; SetArrayLength(PrinterDataArray, Count); // Parse each JSON object and extract printer fields Count := 0; Depth := 0; ObjStart := 0; for I := 1 to Length(Response) do begin if Response[I] = '{' then begin if Depth = 0 then ObjStart := I; Depth := Depth + 1; end else if Response[I] = '}' then begin Depth := Depth - 1; if Depth = 0 then begin ObjStr := Copy(Response, ObjStart, I - ObjStart + 1); Active := JsonGetString(ObjStr, 'isactive'); Vendor := JsonGetString(ObjStr, 'vendor'); if ((Active = '1') or (Active = 'true')) and ((Vendor = 'HP') or (Vendor = 'Xerox')) then begin Name := JsonGetString(ObjStr, 'printerwindowsname'); Model := JsonGetString(ObjStr, 'modelnumber'); Fqdn := JsonGetString(ObjStr, 'fqdn'); Ip := JsonGetString(ObjStr, 'ipaddress'); // Prefer FQDN over IP address if (Fqdn <> '') and (Fqdn <> 'USB') then Address := Fqdn else if Ip <> '' then Address := Ip else Address := ''; if (Name <> '') and (Address <> '') then begin PrinterDataArray[Count].PrinterName := Name; PrinterDataArray[Count].FQDN := Address; PrinterDataArray[Count].Vendor := Vendor; PrinterDataArray[Count].Model := Model; PrinterDataArray[Count].IsInstalled := False; Count := Count + 1; Result := True; end; end; end; end; end; SetArrayLength(PrinterDataArray, Count); end; // ─── Registry-based state checks ───────────────────────────────────────────── // True if the named printer exists in the Windows print subsystem registry. function IsPrinterInstalled(const PrinterName: String): Boolean; begin Result := RegKeyExists(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Print\Printers\' + PrinterName); end; // True if the named print driver is registered in the Windows print environment. function IsDriverInstalled(const DriverName: String; IsX64: Boolean): Boolean; var RegPath: String; begin if IsX64 then RegPath := 'SYSTEM\CurrentControlSet\Control\Print\Environments\Windows x64\Drivers\Version-3\' else RegPath := 'SYSTEM\CurrentControlSet\Control\Print\Environments\Windows NT x86\Drivers\Version-3\'; Result := RegKeyExists(HKEY_LOCAL_MACHINE, RegPath + DriverName); end; // True if a Standard TCP/IP printer port with the given name exists. function IsPortInstalled(const PortName: String): Boolean; begin Result := RegKeyExists(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Print\Monitors\Standard TCP/IP Port\Ports\' + PortName); end; // Update IsInstalled flag for every entry in PrinterDataArray. procedure CheckInstalledPrinters(); var I: Integer; begin for I := 0 to GetArrayLength(PrinterDataArray) - 1 do PrinterDataArray[I].IsInstalled := IsPrinterInstalled(PrinterDataArray[I].PrinterName); end; // ─── Driver staging ────────────────────────────────────────────────────────── // Stage a driver package into the Windows Driver Store using pnputil. // Returns True on success (exit code 0, 259 = no device, or 3010 = reboot needed). function StageDriver(const InfPath: String): Boolean; var ResultCode: Integer; begin Result := False; if not FileExists(InfPath) then Exit; // Windows 10+ syntax if Exec(ExpandConstant('{sys}\pnputil.exe'), '/add-driver "' + InfPath + '" /install', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then begin if (ResultCode = 0) or (ResultCode = 259) or (ResultCode = 3010) then begin Result := True; Exit; end; end; // Fallback: Windows 7 legacy syntax if Exec(ExpandConstant('{sys}\pnputil.exe'), '-i -a "' + InfPath + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then Result := (ResultCode = 0) or (ResultCode = 259) or (ResultCode = 3010); end; // ─── Spooler management ────────────────────────────────────────────────────── // Stop and restart the Windows Print Spooler so it picks up newly staged // drivers and newly created port registry entries. procedure RestartSpooler(); var ResultCode: Integer; begin Exec(ExpandConstant('{sys}\net.exe'), 'stop spooler', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); Sleep(1500); Exec(ExpandConstant('{sys}\net.exe'), 'start spooler', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); Sleep(2000); end; // ─── Driver registration ───────────────────────────────────────────────────── // Register a staged driver with the print subsystem via printui.dll. // Call after pnputil has staged the driver and the spooler has been restarted. procedure InstallDriverViaUI(const DriverName, InfPath: String); var ResultCode: Integer; begin Exec(ExpandConstant('{sys}\rundll32.exe'), 'printui.dll,PrintUIEntry /ia /m "' + DriverName + '" /f "' + InfPath + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); end; // ─── Port management ───────────────────────────────────────────────────────── // Create (or overwrite) a Standard TCP/IP printer port in the registry. // Always writes all values so stale/corrupt entries from previous installs are // replaced with a known-good format. The print spooler must be restarted after // this call for the changes to take effect. function CreateTCPIPPort(const PortAddress: String): Boolean; var RegBase: String; begin RegBase := 'SYSTEM\CurrentControlSet\Control\Print\Monitors\Standard TCP/IP Port\Ports\IP_' + PortAddress; Result := False; if not RegWriteStringValue(HKEY_LOCAL_MACHINE, RegBase, 'HostName', PortAddress) then Exit; if not RegWriteStringValue(HKEY_LOCAL_MACHINE, RegBase, 'IPAddress', PortAddress) then Exit; if not RegWriteDWordValue (HKEY_LOCAL_MACHINE, RegBase, 'PortNumber', 9100) then Exit; if not RegWriteDWordValue (HKEY_LOCAL_MACHINE, RegBase, 'Protocol', 1) then Exit; if not RegWriteDWordValue (HKEY_LOCAL_MACHINE, RegBase, 'Version', 1) then Exit; if not RegWriteStringValue(HKEY_LOCAL_MACHINE, RegBase, 'MonitorName', 'Standard TCP/IP Port') then Exit; if not RegWriteStringValue(HKEY_LOCAL_MACHINE, RegBase, 'Description', 'Generic Network Card') then Exit; if not RegWriteDWordValue (HKEY_LOCAL_MACHINE, RegBase, 'SNMP', 0) then Exit; if not RegWriteDWordValue (HKEY_LOCAL_MACHINE, RegBase, 'Queue', 0) then Exit; Result := True; end; // ─── Port and printer management via WMI ───────────────────────────────────── // Create (or re-register) a Standard TCP/IP printer port. // Always attempts WMI registration with the live spooler — a port may exist in // the registry but be absent from the spooler's live list (stale/orphaned entry), // in which case WMI creation will succeed and bring it back to life. // WMI also writes correct registry values, so CreateTCPIPPort is only used as // a fallback when WMI fails entirely. function CreatePortViaWMI(const PortAddress: String): Boolean; var WMILocator, WMIService, PortClass, NewPort: Variant; PortName: String; begin PortName := 'IP_' + PortAddress; Result := False; // Always try WMI — even if registry key exists, the port may not be in the // spooler's live port list and WMI registration will fix that. // WMI also writes its own registry values which the spooler trusts on restart. try WMILocator := CreateOleObject('WbemScripting.SWbemLocator'); WMIService := WMILocator.ConnectServer('.', 'root\cimv2'); WMIService.Security_.Privileges.AddAsString('SeLoadDriverPrivilege', True); PortClass := WMIService.Get('Win32_TCPIPPrinterPort'); NewPort := PortClass.SpawnInstance_(); NewPort.Name := PortName; NewPort.HostAddress := PortAddress; NewPort.PortNumber := 9100; NewPort.Protocol := 1; NewPort.Put_(); Sleep(500); AddLog(' Port registered with spooler via WMI: ' + PortName); Result := True; except // May fail if port is already live in the spooler — fall back to registry write. AddLog(' WMI port note: ' + GetExceptionMessage); Result := CreateTCPIPPort(PortAddress); if Result then AddLog(' Port written to registry (fallback): ' + PortName) else AddLog(' WARNING: Failed to write port registry values: ' + PortName); end; end; // Add a locally-managed network printer. // First attempts WMI Win32_Printer; falls back to printui.dll /if /q. // InfPath is only used by the printui fallback. function AddNetworkPrinter(const PrinterName, DriverName, PortName, InfPath: String): Boolean; var WMILocator, WMIService, PrinterClass, NewPrinter: Variant; ResultCode: Integer; begin Result := False; if IsPrinterInstalled(PrinterName) then begin Result := True; Exit; end; AddLog(' PrinterName: ' + PrinterName); AddLog(' DriverName: ' + DriverName); AddLog(' PortName: ' + PortName); AddLog(' InfPath: ' + InfPath); if IsPortInstalled(PortName) then AddLog(' Port: present') else AddLog(' Port: MISSING'); if IsDriverInstalled(DriverName, Is64BitInstallMode) then AddLog(' Driver: present') else AddLog(' Driver: MISSING'); if InfPath <> '' then begin if FileExists(InfPath) then AddLog(' INF: found') else AddLog(' INF: NOT FOUND'); end; // Attempt 1: WMI Win32_Printer (DeviceID is the writable key, not Name) AddLog(' Attempting WMI...'); try WMILocator := CreateOleObject('WbemScripting.SWbemLocator'); AddLog(' WMI: locator created'); WMIService := WMILocator.ConnectServer('.', 'root\cimv2'); WMIService.Security_.Privileges.AddAsString('SeLoadDriverPrivilege', True); AddLog(' WMI: connected to root\cimv2'); PrinterClass := WMIService.Get('Win32_Printer'); AddLog(' WMI: got Win32_Printer class'); NewPrinter := PrinterClass.SpawnInstance_(); AddLog(' WMI: spawned instance'); NewPrinter.DeviceID := PrinterName; NewPrinter.DriverName := DriverName; NewPrinter.PortName := PortName; NewPrinter.PrintProcessor := 'winprint'; AddLog(' WMI: properties set, calling Put_()'); NewPrinter.Put_(); AddLog(' WMI: Put_() returned'); Sleep(1000); Result := IsPrinterInstalled(PrinterName); if Result then begin AddLog(' Printer created via WMI'); Exit; end else AddLog(' WMI Put_() returned without error but printer not in registry'); except AddLog(' WMI exception: ' + GetExceptionMessage); end; // Attempt 2: printui.dll /if (silent install from staged INF) if InfPath <> '' then begin AddLog(' Attempting printui /if...'); AddLog(' CMD: rundll32 printui.dll,PrintUIEntry /if /b "' + PrinterName + '" /f "' + InfPath + '" /r "' + PortName + '" /m "' + DriverName + '" /q'); Exec(ExpandConstant('{sys}\rundll32.exe'), 'printui.dll,PrintUIEntry /if /b "' + PrinterName + '" /f "' + InfPath + '" /r "' + PortName + '" /m "' + DriverName + '" /q', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); AddLog(' printui exit code: ' + IntToStr(ResultCode)); Sleep(2000); Result := IsPrinterInstalled(PrinterName); if Result then AddLog(' Printer created via printui') else AddLog(' ERROR: printer not in registry after printui'); end else AddLog(' ERROR: no InfPath available for printui fallback'); end; // Remove a printer via WMI Win32_Printer.Delete_(). function RemoveNetworkPrinter(const PrinterName: String): Boolean; var WMILocator, WMIService, PrinterInst: Variant; begin if not IsPrinterInstalled(PrinterName) then begin Result := True; Exit; end; Result := False; try WMILocator := CreateOleObject('WbemScripting.SWbemLocator'); WMIService := WMILocator.ConnectServer('.', 'root\cimv2'); PrinterInst := WMIService.Get('Win32_Printer.DeviceID="' + PrinterName + '"'); PrinterInst.Delete_(); Sleep(1000); Result := not IsPrinterInstalled(PrinterName); except AddLog(' ERROR: WMI exception removing printer'); Result := False; end; end; // ─── Wizard initialisation ─────────────────────────────────────────────────── procedure InitializeWizard(); var I: Integer; DisplayText: String; AutoSelectPrinter: String; FoundMatch: Boolean; begin if not QueryPrinters() then begin MsgBox('Failed to query printers from ShopDB database.' + #13#10#13#10 + 'Please ensure:' + #13#10 + '- You have network connectivity' + #13#10 + '- The ShopDB server (tsgwp00525.wjs.geaerospace.net) is accessible' + #13#10 + '- You are connected to the GE network' + #13#10 + '- The API endpoint is running', mbError, MB_OK); WizardForm.Close; Exit; end; if GetArrayLength(PrinterDataArray) = 0 then begin MsgBox('No printers found in the database.' + #13#10#13#10 + 'This installer supports HP and Xerox network printers (TCP/IP).' + #13#10#13#10 + 'Verify the ShopDB server is accessible and has active printers.', mbInformation, MB_OK); WizardForm.Close; Exit; end; CheckInstalledPrinters(); // Optional command-line pre-selection: /PRINTER="Name" or /PRINTER="A,B,C" AutoSelectPrinter := ExpandConstant('{param:PRINTER|}'); FoundMatch := False; PrinterSelectionPage := CreateInputOptionPage(wpWelcome, 'Manage Network Printers', 'Select printers to install or uncheck to remove', 'Checked printers will be installed. Unchecked printers that are currently installed will be removed. ' + 'Already-installed printers are pre-checked.', False, False); for I := 0 to GetArrayLength(PrinterDataArray) - 1 do begin if PrinterDataArray[I].PrinterName <> '' then begin if PrinterDataArray[I].IsInstalled then DisplayText := '[INSTALLED] ' else DisplayText := ''; DisplayText := DisplayText + PrinterDataArray[I].PrinterName + ' - ' + PrinterDataArray[I].Vendor + ' ' + PrinterDataArray[I].Model + ' (' + PrinterDataArray[I].FQDN + ')'; PrinterSelectionPage.Add(DisplayText); if AutoSelectPrinter <> '' then begin if Pos(',', AutoSelectPrinter) > 0 then begin if Pos(',' + PrinterDataArray[I].PrinterName + ',', ',' + AutoSelectPrinter + ',') > 0 then begin PrinterSelectionPage.Values[I] := True; FoundMatch := True; end else PrinterSelectionPage.Values[I] := PrinterDataArray[I].IsInstalled; end else begin if Pos(AutoSelectPrinter, PrinterDataArray[I].PrinterName) > 0 then begin PrinterSelectionPage.Values[I] := True; FoundMatch := True; end else PrinterSelectionPage.Values[I] := PrinterDataArray[I].IsInstalled; end; end else PrinterSelectionPage.Values[I] := PrinterDataArray[I].IsInstalled; end; end; if (AutoSelectPrinter <> '') and (not FoundMatch) then MsgBox('Printer "' + AutoSelectPrinter + '" not found in the database.' + #13#10#13#10 + 'Please select a printer from the list manually.', mbInformation, MB_OK); end; // ─── Page validation ───────────────────────────────────────────────────────── function NextButtonClick(CurPageID: Integer): Boolean; var I: Integer; HasChange: Boolean; begin Result := True; if CurPageID = PrinterSelectionPage.ID then begin HasChange := False; for I := 0 to GetArrayLength(PrinterDataArray) - 1 do begin if PrinterSelectionPage.Values[I] <> PrinterDataArray[I].IsInstalled then begin HasChange := True; Break; end; end; if not HasChange then begin MsgBox('No changes detected. Select printers to install or uncheck installed printers to remove them.', mbInformation, MB_OK); Result := False; end; end; end; // ─── Default printer selection ─────────────────────────────────────────────── // Show a dialog listing newly installed printers and return the one the user // picks as default, or '' if they click Skip. function PromptDefaultPrinter(const Names: TArrayOfString): String; var Form: TSetupForm; Lbl: TNewStaticText; List: TNewListBox; BtnSet, BtnSkip: TNewButton; I: Integer; begin Result := ''; if GetArrayLength(Names) = 0 then Exit; // CreateCustomForm(ClientWidth, ClientHeight, KeepSizeX, KeepSizeY) Form := CreateCustomForm(400, 210, False, False); try Form.Caption := 'Set Default Printer'; Form.Position := poScreenCenter; Lbl := TNewStaticText.Create(Form); Lbl.Parent := Form; Lbl.Left := 12; Lbl.Top := 12; Lbl.Width := Form.ClientWidth - 24; Lbl.Caption := 'Select a printer to set as the system default for all users, or click Skip.'; Lbl.WordWrap := True; Lbl.AutoSize := True; List := TNewListBox.Create(Form); List.Parent := Form; List.Left := 12; List.Top := Lbl.Top + Lbl.Height + 8; List.Width := Form.ClientWidth - 24; List.Height := 110; for I := 0 to GetArrayLength(Names) - 1 do List.Items.Add(Names[I]); List.ItemIndex := 0; BtnSet := TNewButton.Create(Form); BtnSet.Parent := Form; BtnSet.Caption := 'Set Default'; BtnSet.Width := 90; BtnSet.Height := 25; BtnSet.Left := Form.ClientWidth - 195; BtnSet.Top := Form.ClientHeight - 37; BtnSet.ModalResult := mrOK; BtnSet.Default := True; BtnSkip := TNewButton.Create(Form); BtnSkip.Parent := Form; BtnSkip.Caption := 'Skip'; BtnSkip.Width := 75; BtnSkip.Height := 25; BtnSkip.Left := Form.ClientWidth - 95; BtnSkip.Top := Form.ClientHeight - 37; BtnSkip.ModalResult := mrCancel; BtnSkip.Cancel := True; if Form.ShowModal() = mrOK then if List.ItemIndex >= 0 then Result := Names[List.ItemIndex]; finally Form.Free; end; end; // Set the Windows default printer for the current user and propagate it to all // other loaded user hives and .DEFAULT. // "Let Windows manage my default printer" (LegacyDefaultPrinterMode=0) causes // printui /y to hang, so we disable it (set to 1) for every profile first. procedure SetDefaultPrinterAllUsers(const PrinterName: String); var ResultCode: Integer; DeviceValue: String; SIDNames: TArrayOfString; I: Integer; WinKey: String; begin WinKey := 'Software\Microsoft\Windows NT\CurrentVersion\Windows'; // Disable "Let Windows manage my default printer" for the current user before // calling printui — otherwise the call hangs when that setting is active. RegWriteDWordValue(HKEY_CURRENT_USER, WinKey, 'LegacyDefaultPrinterMode', 1); // Set default for the current user session Exec(ExpandConstant('{sys}\rundll32.exe'), 'printui.dll,PrintUIEntry /y /n "' + PrinterName + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); AddLog('Set default printer: ' + PrinterName); // Read back the Device string Windows just wrote (format: "Name,winspool,Ne00:") if not RegQueryStringValue(HKEY_CURRENT_USER, WinKey, 'Device', DeviceValue) then Exit; // Propagate both values to .DEFAULT (new user profiles will inherit them) RegWriteDWordValue(HKEY_USERS, '.DEFAULT\' + WinKey, 'LegacyDefaultPrinterMode', 1); RegWriteStringValue(HKEY_USERS, '.DEFAULT\' + WinKey, 'Device', DeviceValue); // Propagate to every other currently-loaded user hive (SIDs, not _Classes keys) if RegGetSubkeyNames(HKEY_USERS, '', SIDNames) then for I := 0 to GetArrayLength(SIDNames) - 1 do if (Pos('S-1-5-', SIDNames[I]) = 1) and (Pos('_Classes', SIDNames[I]) = 0) then begin RegWriteDWordValue(HKEY_USERS, SIDNames[I] + '\' + WinKey, 'LegacyDefaultPrinterMode', 1); RegWriteStringValue(HKEY_USERS, SIDNames[I] + '\' + WinKey, 'Device', DeviceValue); end; end; // ─── DesignJet model detection ─────────────────────────────────────────────── function IsDesignJetModel(const Model: String): Boolean; var L: String; begin L := Lowercase(Model); Result := (Pos('designjet', L) > 0) or (Pos('t1700', L) > 0); end; // ─── Legacy printer cleanup ────────────────────────────────────────────────── // Returns True if the string references the old print server but not the new one. function IsLegacyServerRef(const S: String): Boolean; var L: String; begin L := Lowercase(Trim(S)); Result := (Pos('tsgwp00525', L) > 0) and (Pos('.wjs.', L) = 0); end; // Check a printer's registry key for any sign of the old server across the // values most likely to carry the server name: Port, ShareName, HostName, // Description, the printer name itself. function IsPrinterLegacy(const PrinterName: String): Boolean; var RegBase, Val: String; begin Result := IsLegacyServerRef(PrinterName); if Result then Exit; RegBase := 'SYSTEM\CurrentControlSet\Control\Print\Printers\' + PrinterName; Val := ''; RegQueryStringValue(HKEY_LOCAL_MACHINE, RegBase, 'Port', Val); if IsLegacyServerRef(Val) then begin Result := True; Exit; end; Val := ''; RegQueryStringValue(HKEY_LOCAL_MACHINE, RegBase, 'ShareName', Val); if IsLegacyServerRef(Val) then begin Result := True; Exit; end; Val := ''; RegQueryStringValue(HKEY_LOCAL_MACHINE, RegBase, 'HostName', Val); if IsLegacyServerRef(Val) then begin Result := True; Exit; end; Val := ''; RegQueryStringValue(HKEY_LOCAL_MACHINE, RegBase, 'Description', Val); if IsLegacyServerRef(Val) then begin Result := True; Exit; end; end; // Remove any printers whose registry data references the old print servers // (tsgwp00525 / tsgwp00525.rd.ds.ge.com / tsgwp00525.logon.ds.ge.com), // excluding the current server (tsgwp00525.wjs.geaerospace.net). // Deletes registry keys directly with the spooler stopped to avoid WMI hanging // while trying to contact the unreachable old server. // Returns the number of printers removed. function CleanupLegacyPrinters(): Integer; var PrinterNames, LegacyFound_ConnKeys: TArrayOfString; I, J: Integer; PrinterName, PortValue: String; LegacyFound: Boolean; ResultCode: Integer; begin Result := 0; AddLog('Checking for legacy printers from old servers...'); if not RegGetSubkeyNames(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Print\Printers', PrinterNames) then begin AddLog(' Could not enumerate installed printers'); Exit; end; // First pass: check whether any legacy printers exist before stopping spooler LegacyFound := False; for I := 0 to GetArrayLength(PrinterNames) - 1 do if IsPrinterLegacy(PrinterNames[I]) then begin LegacyFound := True; Break; end; if not LegacyFound then begin AddLog(' No legacy printers found'); Exit; end; // Stop the spooler so registry keys are not locked — avoids any network contact // with the old (unreachable) print server that would cause WMI to hang. AddLog(' Legacy printers found — stopping spooler for registry cleanup...'); Exec(ExpandConstant('{sys}\net.exe'), 'stop spooler /y', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); Sleep(2000); // Second pass: delete registry keys for each legacy printer for I := 0 to GetArrayLength(PrinterNames) - 1 do begin PrinterName := PrinterNames[I]; if IsPrinterLegacy(PrinterName) then begin PortValue := ''; RegQueryStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Print\Printers\' + PrinterName, 'Port', PortValue); AddLog(' Removing: ' + PrinterName + ' (port: ' + PortValue + ')'); Exec(ExpandConstant('{sys}\reg.exe'), 'delete "HKLM\SYSTEM\CurrentControlSet\Control\Print\Printers\' + PrinterName + '" /f', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); if ResultCode = 0 then begin AddLog(' Removed: ' + PrinterName); Result := Result + 1; end else AddLog(' WARNING: reg delete returned ' + IntToStr(ResultCode) + ' for ' + PrinterName); end; end; // Also remove HKCU\Printers\Connections entries — network-connected printers // store a connection record here that causes Windows to re-add the printer // from the old server when the spooler restarts, even after the HKLM key // is gone. Connection key names use commas instead of backslashes: // \\tsgwp00525\share → ,,tsgwp00525,share AddLog(' Checking for legacy printer connections in user hives...'); if RegGetSubkeyNames(HKEY_USERS, '.DEFAULT\Printers\Connections', PrinterNames) then for I := 0 to GetArrayLength(PrinterNames) - 1 do if IsLegacyServerRef(PrinterNames[I]) then begin AddLog(' Removing .DEFAULT connection: ' + PrinterNames[I]); Exec(ExpandConstant('{sys}\reg.exe'), 'delete "HKU\.DEFAULT\Printers\Connections\' + PrinterNames[I] + '" /f', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); end; // Reuse PrinterNames var to enumerate SIDs if RegGetSubkeyNames(HKEY_USERS, '', PrinterNames) then for I := 0 to GetArrayLength(PrinterNames) - 1 do if (Pos('S-1-5-', PrinterNames[I]) = 1) and (Pos('_Classes', PrinterNames[I]) = 0) then begin if RegGetSubkeyNames(HKEY_USERS, PrinterNames[I] + '\Printers\Connections', LegacyFound_ConnKeys) then for J := 0 to GetArrayLength(LegacyFound_ConnKeys) - 1 do if IsLegacyServerRef(LegacyFound_ConnKeys[J]) then begin AddLog(' Removing connection for ' + PrinterNames[I] + ': ' + LegacyFound_ConnKeys[J]); Exec(ExpandConstant('{sys}\reg.exe'), 'delete "HKU\' + PrinterNames[I] + '\Printers\Connections\' + LegacyFound_ConnKeys[J] + '" /f', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); end; end; // Restart spooler — the main flow will restart it again if needed AddLog(' Restarting spooler after cleanup...'); Exec(ExpandConstant('{sys}\net.exe'), 'start spooler', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); Sleep(2000); end; // ─── Progress helpers ──────────────────────────────────────────────────────── // Update the status label and advance the progress bar during post-install. // Call SetStatusInit first to reset the bar, then SetStatus for each step. var GaugeStep, GaugeTotal: Integer; procedure SetStatusInit(Total: Integer); begin GaugeTotal := Total; GaugeStep := 0; WizardForm.ProgressGauge.Min := 0; WizardForm.ProgressGauge.Max := Total; WizardForm.ProgressGauge.Position := 0; end; procedure SetStatus(const Msg: String); begin if GaugeStep < GaugeTotal then GaugeStep := GaugeStep + 1; WizardForm.ProgressGauge.Position := GaugeStep; WizardForm.StatusLabel.Caption := Msg; WizardForm.FileNameLabel.Caption := ''; Application.ProcessMessages; end; // ─── Main installation ─────────────────────────────────────────────────────── procedure CurStepChanged(CurStep: TSetupStep); var I: Integer; HasChanges, HasHP, HasXerox, HasDesignJet, NeedSpoolerRestart: Boolean; IsX64: Boolean; HPInfPath, XeroxInfPath, HPDriverDir, DesignJetInfPath: String; InstalledCount, RemovedCount, FailCount: Integer; DriverName, PortName, PortAddress, InfPath: String; LogDir, LogPath, SummaryMsg: String; InstalledNames: TArrayOfString; DefaultPrinter: String; AllCount, CleanedCount: Integer; begin if CurStep <> ssPostInstall then Exit; // Check whether anything actually needs to change HasChanges := False; for I := 0 to GetArrayLength(PrinterDataArray) - 1 do begin if PrinterSelectionPage.Values[I] <> PrinterDataArray[I].IsInstalled then begin HasChanges := True; Break; end; end; if not HasChanges then Exit; InstallLog := TStringList.Create; try AddLog('WJDT Printer Installer starting...'); LogDir := ExpandConstant('{userdocs}\wjdt\logs'); ForceDirectories(LogDir); LogPath := LogDir + '\printer_install.log'; // Initialise progress bar: fixed steps + one per printer being changed begin var StepCount: Integer; StepCount := 4; // cleanup + staging + 2x spooler restart for I := 0 to GetArrayLength(PrinterDataArray) - 1 do if PrinterSelectionPage.Values[I] <> PrinterDataArray[I].IsInstalled then StepCount := StepCount + 1; SetStatusInit(StepCount); end; // ── Step 0: Remove legacy printers from old print servers ──────────────── SetStatus('Removing legacy printers...'); CleanedCount := CleanupLegacyPrinters(); IsX64 := Is64BitInstallMode; if IsX64 then begin HPDriverDir := ExpandConstant('{tmp}\hp_drivers_x64'); HPInfPath := HPDriverDir + '\hpcu355u.inf'; XeroxInfPath := ExpandConstant('{tmp}\xerox_drivers_x64\UNIV_5.1076.3.0_PCL6_x64_Driver.inf\x3UNIVX.inf'); DesignJetInfPath := ExpandConstant('{tmp}\hp_designjet_x64\hpi94dev.inf'); end else begin HPDriverDir := ExpandConstant('{tmp}\hp_drivers_x32'); HPInfPath := HPDriverDir + '\hpcu355c.inf'; XeroxInfPath := ExpandConstant('{tmp}\xerox_drivers_x32\UNIV_5.1076.3.0_PCL6_x86_Driver.inf\x3UNIVX.inf'); DesignJetInfPath := ExpandConstant('{tmp}\hp_designjet_x32\hpi94ded.inf'); end; // Determine which vendor drivers are needed for new installs HasHP := False; HasXerox := False; HasDesignJet := False; for I := 0 to GetArrayLength(PrinterDataArray) - 1 do begin if (not PrinterDataArray[I].IsInstalled) and PrinterSelectionPage.Values[I] then begin if PrinterDataArray[I].Vendor = 'Xerox' then HasXerox := True else if PrinterDataArray[I].Vendor = 'HP' then begin if IsDesignJetModel(PrinterDataArray[I].Model) then HasDesignJet := True else HasHP := True; end; end; end; NeedSpoolerRestart := False; // ── Step 1: Stage drivers with pnputil ────────────────────────────────── SetStatus('Staging printer drivers...'); AddLog('Step 1: Staging printer drivers...'); if HasHP then begin if IsDriverInstalled('HP Universal Printing PCL 6', IsX64) then AddLog(' HP driver already installed') else begin AddLog(' Staging HP Universal Printing PCL 6...'); if StageDriver(HPInfPath) then begin AddLog(' HP driver staged OK'); NeedSpoolerRestart := True; end else AddLog(' WARNING: HP staging failed — INF: ' + HPInfPath); end; end; if HasDesignJet then begin if IsDriverInstalled('HP DesignJet T1700dr PS3', IsX64) then AddLog(' HP DesignJet T1700dr PS3 driver already installed') else begin AddLog(' Staging HP DesignJet T1700dr PS3...'); if StageDriver(DesignJetInfPath) then begin AddLog(' HP DesignJet driver staged OK'); NeedSpoolerRestart := True; end else AddLog(' WARNING: HP DesignJet staging failed — INF: ' + DesignJetInfPath); end; end; if HasXerox then begin if IsDriverInstalled('Xerox Global Print Driver PCL6', IsX64) then AddLog(' Xerox driver already installed') else begin AddLog(' Staging Xerox Global Print Driver PCL6...'); if StageDriver(XeroxInfPath) then begin AddLog(' Xerox driver staged OK'); NeedSpoolerRestart := True; end else AddLog(' WARNING: Xerox staging failed — INF: ' + XeroxInfPath); end; end; // ── Step 2: Restart spooler to register staged drivers ────────────────── if NeedSpoolerRestart then begin SetStatus('Restarting print spooler...'); AddLog('Step 2: Restarting print spooler to register staged drivers...'); RestartSpooler(); AddLog(' Spooler restarted'); end; // ── Step 3: Create TCP/IP ports ────────────────────────────────────────── SetStatus('Creating printer ports...'); AddLog('Step 3: Creating printer ports...'); for I := 0 to GetArrayLength(PrinterDataArray) - 1 do begin if (not PrinterDataArray[I].IsInstalled) and PrinterSelectionPage.Values[I] then begin PortAddress := PrinterDataArray[I].FQDN; PortName := 'IP_' + PortAddress; if IsPortInstalled(PortName) then AddLog(' Port exists (re-registering with spooler): ' + PortName) else AddLog(' Creating port: ' + PortName); // Always call CreatePortViaWMI — it handles both new and stale-existing // ports by attempting live WMI registration and refreshing registry values. CreatePortViaWMI(PortAddress); end; end; // ── Step 4: Register drivers with print subsystem (fallback after restart) ─ if HasHP and not IsDriverInstalled('HP Universal Printing PCL 6', IsX64) then begin AddLog(' Registering HP driver via printui...'); InstallDriverViaUI('HP Universal Printing PCL 6', HPInfPath); if IsDriverInstalled('HP Universal Printing PCL 6', IsX64) then AddLog(' HP driver registered OK') else AddLog(' WARNING: HP driver not confirmed after registration'); end; if HasDesignJet and not IsDriverInstalled('HP DesignJet T1700dr PS3', IsX64) then begin AddLog(' Registering HP DesignJet driver via printui...'); InstallDriverViaUI('HP DesignJet T1700dr PS3', DesignJetInfPath); if IsDriverInstalled('HP DesignJet T1700dr PS3', IsX64) then AddLog(' HP DesignJet driver registered OK') else AddLog(' WARNING: HP DesignJet driver not confirmed after registration'); end; if HasXerox and not IsDriverInstalled('Xerox Global Print Driver PCL6', IsX64) then begin AddLog(' Registering Xerox driver via printui...'); InstallDriverViaUI('Xerox Global Print Driver PCL6', XeroxInfPath); if IsDriverInstalled('Xerox Global Print Driver PCL6', IsX64) then AddLog(' Xerox driver registered OK') else AddLog(' WARNING: Xerox driver not confirmed after registration'); end; // ── Step 5: Restart spooler before printer changes to ensure all ports // and drivers are fully loaded in the live spooler session ────── SetStatus('Restarting print spooler...'); AddLog('Step 5: Restarting spooler before printer installation...'); RestartSpooler(); AddLog(' Spooler restarted'); // ── Step 6: Install / remove printers ─────────────────────────────────── InstalledCount := 0; RemovedCount := 0; FailCount := 0; SetArrayLength(InstalledNames, GetArrayLength(PrinterDataArray)); AddLog('Step 6: Processing printer changes...'); for I := 0 to GetArrayLength(PrinterDataArray) - 1 do begin // Remove: currently installed but now unchecked if PrinterDataArray[I].IsInstalled and (not PrinterSelectionPage.Values[I]) then begin SetStatus('Removing: ' + PrinterDataArray[I].PrinterName); AddLog('Removing: ' + PrinterDataArray[I].PrinterName); if RemoveNetworkPrinter(PrinterDataArray[I].PrinterName) then begin AddLog(' Removed successfully'); RemovedCount := RemovedCount + 1; end else begin AddLog(' FAILED to remove: ' + PrinterDataArray[I].PrinterName); FailCount := FailCount + 1; end; end // Install: not installed but now checked else if (not PrinterDataArray[I].IsInstalled) and PrinterSelectionPage.Values[I] then begin SetStatus('Installing: ' + PrinterDataArray[I].PrinterName); AddLog('Installing: ' + PrinterDataArray[I].PrinterName); if PrinterDataArray[I].Vendor = 'HP' then begin if IsDesignJetModel(PrinterDataArray[I].Model) then begin DriverName := 'HP DesignJet T1700dr PS3'; InfPath := DesignJetInfPath; end else begin DriverName := 'HP Universal Printing PCL 6'; InfPath := HPInfPath; end; end else if PrinterDataArray[I].Vendor = 'Xerox' then begin DriverName := 'Xerox Global Print Driver PCL6'; InfPath := XeroxInfPath; end else begin DriverName := ''; InfPath := ''; end; if DriverName = '' then AddLog(' SKIPPED: unsupported vendor ' + PrinterDataArray[I].Vendor) else begin PortAddress := PrinterDataArray[I].FQDN; PortName := 'IP_' + PortAddress; if not IsPortInstalled(PortName) then AddLog(' WARNING: port ' + PortName + ' not found'); if not IsDriverInstalled(DriverName, IsX64) then AddLog(' WARNING: driver "' + DriverName + '" not found'); if AddNetworkPrinter(PrinterDataArray[I].PrinterName, DriverName, PortName, InfPath) then begin AddLog(' Installed successfully'); InstalledNames[InstalledCount] := PrinterDataArray[I].PrinterName; InstalledCount := InstalledCount + 1; end else begin AddLog(' FAILED to install'); FailCount := FailCount + 1; end; end; end; // Unchanged entries: skip end; // Build list of ALL currently installed printers (pre-existing + just installed) // so the user can pick any of them as the new default. SetArrayLength(InstalledNames, GetArrayLength(PrinterDataArray)); AllCount := 0; for I := 0 to GetArrayLength(PrinterDataArray) - 1 do if IsPrinterInstalled(PrinterDataArray[I].PrinterName) then begin InstalledNames[AllCount] := PrinterDataArray[I].PrinterName; AllCount := AllCount + 1; end; SetArrayLength(InstalledNames, AllCount); DefaultPrinter := ''; if InstalledCount > 0 then begin DefaultPrinter := PromptDefaultPrinter(InstalledNames); if DefaultPrinter <> '' then SetDefaultPrinterAllUsers(DefaultPrinter); end; // Fill bar to completion WizardForm.ProgressGauge.Position := WizardForm.ProgressGauge.Max; WizardForm.StatusLabel.Caption := 'Installation complete.'; WizardForm.FileNameLabel.Caption := ''; Application.ProcessMessages; // Save log InstallLog.SaveToFile(LogPath); // Show summary SummaryMsg := 'Printer installation complete!' + #13#10 + #13#10; if InstalledCount > 0 then SummaryMsg := SummaryMsg + 'Installed: ' + IntToStr(InstalledCount) + #13#10; if RemovedCount > 0 then SummaryMsg := SummaryMsg + 'Removed: ' + IntToStr(RemovedCount) + #13#10; if CleanedCount > 0 then SummaryMsg := SummaryMsg + 'Cleaned: ' + IntToStr(CleanedCount) + ' legacy printer(s)' + #13#10; if FailCount > 0 then SummaryMsg := SummaryMsg + 'Failed: ' + IntToStr(FailCount) + #13#10; if DefaultPrinter <> '' then SummaryMsg := SummaryMsg + 'Default: ' + DefaultPrinter + #13#10; MsgBox(SummaryMsg, mbInformation, MB_OK); finally InstallLog.Free; InstallLog := nil; end; end; // ─── Page skip ─────────────────────────────────────────────────────────────── function ShouldSkipPage(PageID: Integer): Boolean; begin Result := False; if PageID = wpSelectDir then Result := True; end;