Files
inno-installers/PrinterInstaller/PrinterInstaller.iss
cproudlock d68cd6fe02 Fix var declaration inside begin..end block (Pascal syntax error)
Move StepCount to procedure var section.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 17:13:30 -05:00

1253 lines
47 KiB
Plaintext

; 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 := '';
WizardForm.Refresh;
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, StepCount: 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
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);
// ── 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 := '';
WizardForm.Refresh;
// 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;