; Universal Printer Installer for Network Printers
; Installs HP/Xerox/Brother 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/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 PS x64
Source: "drivers\hp_x64\*"; DestDir: "{tmp}\hp_drivers_x64"; Flags: ignoreversion recursesubdirs; Check: 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

; Xerox Universal Print Driver x32
Source: "drivers\xerox_x32\*"; DestDir: "{tmp}\xerox_drivers_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
  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') or (Vendor = 'Brother')) 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;

// ─── 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.
// 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;

// 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
// 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, 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;
    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 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;
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 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;
  I, J: Integer;
  PrinterName, PortValue: String;
  LegacyFound: Boolean;
  ResultCode: Integer;
begin
  Result := 0;
  AddLog('Checking for server-connected printers on tsgwp00525...');

  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
  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 server-connected printers found');
    Exit;
  end;

  // 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];
    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}\rundll32.exe'),
           'printui.dll,PrintUIEntry /dl /n "' + PrinterName + '"',
           '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
      AddLog('  printui /dl exit code: ' + IntToStr(ResultCode));
      Sleep(500);
      if not IsPrinterInstalled(PrinterName) then
      begin
        AddLog('  Removed: ' + PrinterName);
        Result := Result + 1;
      end
      else
        AddLog('  printui did not remove: ' + PrinterName + ' — will retry via registry');
    end;
  end;

  // 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('  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
      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;

  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;

  // 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 ────────────────────────────────────────────────────────

// 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, 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;
  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 + '\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      := '';
      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
          else HasHP := True;
        end;
      end;
    end;

    NeedSpoolerRestart := False;

    // ── 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('Staging printer drivers...');

    if HasHP then
    begin
      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 PS...');
        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;

    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
      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 IsX64 and not IsDriverInstalled('HP Universal Printing PS', IsX64) then
    begin
      AddLog('  Registering HP driver via printui...');
      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');
    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 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 PS';
            InfPath    := HPInfPath;
          end;
        end
        else if PrinterDataArray[I].Vendor = 'Xerox' then
        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 := '';
          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;
