Add OpenText/brother installers and update printer installer scripts

- New PrinterInstaller support for Brother MFC-J series (driver source,
  DSI files; binaries gitignored)
- New OpenText HostExplorer 15 installer (profiles, shortcuts, .iss;
  .exe/.cab/.msi gitignored)
- New HP printer driver support files (.gpd/.cfg/.xml/.dtd) for 3556
  model series
- PrinterInstaller.iss rework with additional driver paths
- FQDNUpdate, MachineAuth, XeroxOfflineInstaller minor updates

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-04-17 11:58:52 -04:00
parent 3776ed9fd2
commit cc36182352
118 changed files with 105378 additions and 103 deletions

View File

@@ -1,5 +1,5 @@
; Universal Printer Installer for Network Printers
; Installs HP/Xerox network printers from ShopDB via TCP/IP
; Installs HP/Xerox/Brother network printers from ShopDB via TCP/IP
; Pure Inno Setup Pascal Script - no PowerShell dependency
[Setup]
@@ -29,14 +29,17 @@ DisableWelcomePage=no
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.
WelcomeLabel2=This utility will install network printers.%n%nFeatures:%n- Query HP/Xerox/Brother printers from ShopDB%n- Install via TCP/IP using FQDN or IP address%n- Automatic driver installation (HP Universal PS, Xerox Global Print Driver, Brother MFC)%n%nClick Next to continue.
[Files]
; HP Universal Print Driver x64
; HP Universal Print Driver PS 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
; 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
; Xerox Universal Print Driver x64
Source: "drivers\xerox_x64\*"; DestDir: "{tmp}\xerox_drivers_x64"; Flags: ignoreversion recursesubdirs; Check: Is64BitInstallMode
@@ -44,11 +47,8 @@ Source: "drivers\xerox_x64\*"; DestDir: "{tmp}\xerox_drivers_x64"; Flags: ignore
; 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
; Brother MFC Printer Driver (multi-architecture package)
Source: "drivers\brother\*"; DestDir: "{tmp}\brother_drivers"; Flags: ignoreversion recursesubdirs
[Code]
var
@@ -195,7 +195,7 @@ begin
Vendor := JsonGetString(ObjStr, 'vendor');
if ((Active = '1') or (Active = 'true')) and
((Vendor = 'HP') or (Vendor = 'Xerox')) then
((Vendor = 'HP') or (Vendor = 'Xerox') or (Vendor = 'Brother')) then
begin
Name := JsonGetString(ObjStr, 'printerwindowsname');
Model := JsonGetString(ObjStr, 'modelnumber');
@@ -265,6 +265,25 @@ begin
PrinterDataArray[I].IsInstalled := IsPrinterInstalled(PrinterDataArray[I].PrinterName);
end;
// ─── Certificate trust ───────────────────────────────────────────────────────
// Add the signing certificate from a .cat file to the Trusted Publishers store
// so that pnputil does not prompt the user to trust the driver.
procedure TrustCatalogCert(const CatPath: String);
var
ResultCode: Integer;
begin
if not FileExists(CatPath) then Exit;
// Export signer cert from catalog, then add to TrustedPublisher (machine store)
Exec(ExpandConstant('{sys}\cmd.exe'),
'/c certutil -addstore -f TrustedPublisher "' + CatPath + '"',
'', SW_HIDE, ewWaitUntilTerminated, ResultCode);
if ResultCode = 0 then
AddLog(' Trusted cert from: ' + ExtractFileName(CatPath))
else
AddLog(' Note: certutil returned ' + IntToStr(ResultCode) + ' for ' + ExtractFileName(CatPath));
end;
// ─── Driver staging ──────────────────────────────────────────────────────────
// Stage a driver package into the Windows Driver Store using pnputil.
@@ -295,6 +314,29 @@ begin
Result := (ResultCode = 0) or (ResultCode = 259) or (ResultCode = 3010);
end;
// Stage Brother MFC driver via DPInst (proprietary GDI driver, no Printer class INF).
function StageBrotherDriver(const DriverDir: String): Boolean;
var
DPInstExe: String;
ResultCode: Integer;
begin
Result := False;
if Is64BitInstallMode then
DPInstExe := DriverDir + '\dpinstx64.exe'
else
DPInstExe := DriverDir + '\dpinstx86.exe';
if not FileExists(DPInstExe) then
begin
AddLog(' DPInst not found: ' + DPInstExe);
Exit;
end;
// DPInst reads dpinst.xml (quietInstall, forceIfDriverIsNotBetter)
if Exec(DPInstExe, '/Q /SA /SE', DriverDir, SW_HIDE, ewWaitUntilTerminated, ResultCode) then
Result := (ResultCode >= 0); // DPInst returns number of drivers installed, or negative on error
end;
// ─── Spooler management ──────────────────────────────────────────────────────
// Stop and restart the Windows Print Spooler so it picks up newly staged
@@ -514,7 +556,7 @@ begin
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 +
'This installer supports HP, Xerox, and Brother network printers (TCP/IP).' + #13#10#13#10 +
'Verify the ShopDB server is accessible and has active printers.',
mbInformation, MB_OK);
WizardForm.Close;
@@ -735,12 +777,13 @@ end;
// ─── Legacy printer cleanup ──────────────────────────────────────────────────
// Returns True if the string references the old print server but not the new one.
// Returns True if the string references the print server (any FQDN variant).
// All server-connected printers should be removed since we now use direct TCP/IP.
function IsLegacyServerRef(const S: String): Boolean;
var L: String;
begin
L := Lowercase(Trim(S));
Result := (Pos('tsgwp00525', L) > 0) and (Pos('.wjs.', L) = 0);
Result := Pos('tsgwp00525', L) > 0;
end;
// Check a printer's registry key for any sign of the old server across the
@@ -761,12 +804,10 @@ begin
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.
// Remove any printers whose registry data references the tsgwp00525 print server
// (any FQDN variant). Uses printui /dl with the spooler running first (proper
// Windows API removal), then cleans up connection entries, then does registry
// fallback for anything that survives. Returns the number of printers removed.
function CleanupLegacyPrinters(): Integer;
var
PrinterNames, LegacyFound_ConnKeys: TArrayOfString;
@@ -776,7 +817,7 @@ var
ResultCode: Integer;
begin
Result := 0;
AddLog('Checking for legacy printers from old servers...');
AddLog('Checking for server-connected printers on tsgwp00525...');
if not RegGetSubkeyNames(HKEY_LOCAL_MACHINE,
'SYSTEM\CurrentControlSet\Control\Print\Printers', PrinterNames) then
@@ -785,7 +826,7 @@ begin
Exit;
end;
// First pass: check whether any legacy printers exist before stopping spooler
// First pass: check whether any legacy printers exist
LegacyFound := False;
for I := 0 to GetArrayLength(PrinterNames) - 1 do
if IsPrinterLegacy(PrinterNames[I]) then
@@ -796,18 +837,14 @@ begin
if not LegacyFound then
begin
AddLog(' No legacy printers found');
AddLog(' No server-connected 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
// Phase 1: Delete printers via printui with spooler RUNNING.
// This is the proper Windows API for printer removal and handles
// both per-machine and per-user printers reliably.
AddLog(' Phase 1: Removing printers via printui (spooler running)...');
for I := 0 to GetArrayLength(PrinterNames) - 1 do
begin
PrinterName := PrinterNames[I];
@@ -818,27 +855,26 @@ begin
'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',
Exec(ExpandConstant('{sys}\rundll32.exe'),
'printui.dll,PrintUIEntry /dl /n "' + PrinterName + '"',
'', SW_HIDE, ewWaitUntilTerminated, ResultCode);
if ResultCode = 0 then
AddLog(' printui /dl exit code: ' + IntToStr(ResultCode));
Sleep(500);
if not IsPrinterInstalled(PrinterName) then
begin
AddLog(' Removed: ' + PrinterName);
Result := Result + 1;
end
else
AddLog(' WARNING: reg delete returned ' + IntToStr(ResultCode) +
' for ' + PrinterName);
AddLog(' printui did not remove: ' + PrinterName + ' — will retry via registry');
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:
// Phase 2: Clean up connection entries in all user hives.
// These cause the spooler to re-add server printers on restart.
// Connection key names use commas instead of backslashes:
// \\tsgwp00525\share → ,,tsgwp00525,share
AddLog(' Checking for legacy printer connections in user hives...');
AddLog(' Phase 2: Cleaning up 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
@@ -849,7 +885,6 @@ begin
'', 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
@@ -867,11 +902,54 @@ begin
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);
// Phase 3: Registry fallback — stop spooler and force-delete any printer
// keys that survived printui removal.
if not RegGetSubkeyNames(HKEY_LOCAL_MACHINE,
'SYSTEM\CurrentControlSet\Control\Print\Printers', PrinterNames) then
SetArrayLength(PrinterNames, 0);
LegacyFound := False;
for I := 0 to GetArrayLength(PrinterNames) - 1 do
if IsPrinterLegacy(PrinterNames[I]) then
begin
LegacyFound := True;
Break;
end;
if LegacyFound then
begin
AddLog(' Phase 3: Stopping spooler for registry cleanup of remaining printers...');
Exec(ExpandConstant('{sys}\net.exe'), 'stop spooler /y',
'', SW_HIDE, ewWaitUntilTerminated, ResultCode);
Sleep(2000);
for I := 0 to GetArrayLength(PrinterNames) - 1 do
begin
PrinterName := PrinterNames[I];
if IsPrinterLegacy(PrinterName) then
begin
AddLog(' Registry cleanup: ' + PrinterName);
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 via registry: ' + PrinterName);
Result := Result + 1;
end
else
AddLog(' WARNING: reg delete returned ' + IntToStr(ResultCode) +
' for ' + PrinterName);
end;
end;
Exec(ExpandConstant('{sys}\net.exe'), 'start spooler',
'', SW_HIDE, ewWaitUntilTerminated, ResultCode);
Sleep(2000);
end;
AddLog(' Cleanup complete: ' + IntToStr(Result) + ' printer(s) removed');
end;
// ─── Progress helpers ────────────────────────────────────────────────────────
@@ -905,9 +983,10 @@ end;
procedure CurStepChanged(CurStep: TSetupStep);
var
I: Integer;
HasChanges, HasHP, HasXerox, HasDesignJet, NeedSpoolerRestart: Boolean;
HasChanges, HasHP, HasXerox, HasDesignJet, HasBrother, NeedSpoolerRestart: Boolean;
IsX64: Boolean;
HPInfPath, XeroxInfPath, HPDriverDir, DesignJetInfPath: String;
BrotherDriverDir, BrotherInfPath: String;
InstalledCount, RemovedCount, FailCount: Integer;
DriverName, PortName, PortAddress, InfPath: String;
LogDir, LogPath, SummaryMsg: String;
@@ -952,28 +1031,33 @@ begin
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');
HPDriverDir := ExpandConstant('{tmp}\hp_drivers_x64');
HPInfPath := HPDriverDir + '\hpcu355v.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';
HPDriverDir := '';
HPInfPath := '';
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;
BrotherDriverDir := ExpandConstant('{tmp}\brother_drivers');
BrotherInfPath := BrotherDriverDir + '\brpoi16a.inf';
// Determine which vendor drivers are needed for new installs
HasHP := False;
HasXerox := False;
HasDesignJet := False;
HasBrother := 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 = 'Brother' then HasBrother := True
else if PrinterDataArray[I].Vendor = 'HP' then
begin
if IsDesignJetModel(PrinterDataArray[I].Model) then HasDesignJet := True
@@ -984,17 +1068,32 @@ begin
NeedSpoolerRestart := False;
// ── Step 1: Stage drivers with pnputil ──────────────────────────────────
// ── Step 1: Trust driver certificates and stage with pnputil ───────────
SetStatus('Trusting driver certificates...');
AddLog('Step 1: Trusting driver certificates...');
if HasHP and IsX64 then
TrustCatalogCert(HPDriverDir + '\hpcu355v.cat');
if HasDesignJet then
begin
if IsX64 then
TrustCatalogCert(ExpandConstant('{tmp}\hp_designjet_x64') + '\hpi94dev.cat')
else
TrustCatalogCert(ExpandConstant('{tmp}\hp_designjet_x32') + '\hpi94ded.cat');
end;
SetStatus('Staging printer drivers...');
AddLog('Step 1: Staging printer drivers...');
AddLog('Staging printer drivers...');
if HasHP then
begin
if IsDriverInstalled('HP Universal Printing PCL 6', IsX64) then
if not IsX64 then
AddLog(' HP PS driver is x64 only — skipping HP staging on 32-bit')
else if IsDriverInstalled('HP Universal Printing PS', IsX64) then
AddLog(' HP driver already installed')
else
begin
AddLog(' Staging HP Universal Printing PCL 6...');
AddLog(' Staging HP Universal Printing PS...');
if StageDriver(HPInfPath) then
begin
AddLog(' HP driver staged OK');
@@ -1039,6 +1138,18 @@ begin
end;
end;
if HasBrother then
begin
AddLog(' Staging Brother MFC driver via DPInst...');
if StageBrotherDriver(BrotherDriverDir) then
begin
AddLog(' Brother driver staged OK');
NeedSpoolerRestart := True;
end
else
AddLog(' WARNING: Brother DPInst staging failed');
end;
// ── Step 2: Restart spooler to register staged drivers ──────────────────
if NeedSpoolerRestart then
begin
@@ -1071,11 +1182,11 @@ begin
end;
// ── Step 4: Register drivers with print subsystem (fallback after restart) ─
if HasHP and not IsDriverInstalled('HP Universal Printing PCL 6', IsX64) then
if HasHP and IsX64 and not IsDriverInstalled('HP Universal Printing PS', 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
InstallDriverViaUI('HP Universal Printing PS', HPInfPath);
if IsDriverInstalled('HP Universal Printing PS', IsX64) then
AddLog(' HP driver registered OK')
else
AddLog(' WARNING: HP driver not confirmed after registration');
@@ -1148,9 +1259,15 @@ begin
DriverName := 'HP DesignJet T1700dr PS3';
InfPath := DesignJetInfPath;
end
else if not IsX64 then
begin
AddLog(' SKIPPED: HP PS driver is x64 only — ' + PrinterDataArray[I].PrinterName);
FailCount := FailCount + 1;
Continue;
end
else
begin
DriverName := 'HP Universal Printing PCL 6';
DriverName := 'HP Universal Printing PS';
InfPath := HPInfPath;
end;
end
@@ -1159,6 +1276,11 @@ begin
DriverName := 'Xerox Global Print Driver PCL6';
InfPath := XeroxInfPath;
end
else if PrinterDataArray[I].Vendor = 'Brother' then
begin
DriverName := 'Brother MFC-J5845DW';
InfPath := BrotherInfPath;
end
else
begin
DriverName := '';