- Add HP DesignJet T1700dr PS3 driver (hpi94dev/hpi94ded.inf) with model-based selection: printers with T1700/DesignJet in model use DesignJet driver - Fix legacy printer reappearing after cleanup: also remove HKCU\Printers\Connections entries across all loaded user hives and .DEFAULT (Windows re-adds printers from these connection records when spooler restarts) - Add IsPrinterLegacy()/IsLegacyServerRef() helpers for robust legacy detection (checks Port, ShareName, HostName, Description registry values with Trim/Lowercase) - Add default printer selection dialog (all installed printers, all user profiles) - Fix printui /y hang by setting LegacyDefaultPrinterMode=1 before calling it Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1207 lines
45 KiB
Plaintext
1207 lines
45 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;
|
|
|
|
// ─── 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';
|
|
|
|
// ── Step 0: Remove legacy printers from old print servers ────────────────
|
|
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 ──────────────────────────────────
|
|
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
|
|
AddLog('Step 2: Restarting print spooler to register staged drivers...');
|
|
RestartSpooler();
|
|
AddLog(' Spooler restarted');
|
|
end;
|
|
|
|
// ── Step 3: Create TCP/IP 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 ──────
|
|
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
|
|
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
|
|
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;
|
|
|
|
// 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;
|