#define MyAppName "Raspberry Pi Imager"
#define MyAppVersion "@IMAGER_VERSION_STR@"
#define MyAppPublisher "Raspberry Pi Ltd"
#define RaspberryPiSoftwareURL "https://www.raspberrypi.com/software"
#define ImagerRepoRootURL "https://github.com/raspberrypi/rpi-imager"
#define ImagerRepoIssuesURL "https://github.com/raspberrypi/rpi-imager/issues"
#define MyAppExeName "rpi-imager.exe"

[Setup]
AppId={{46C75BF3-E267-4834-AE1D-BEB77E05BFA1}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#RaspberryPiSoftwareURL}
AppSupportURL={#ImagerRepoIssuesURL}
AppUpdatesURL={#ImagerRepoRootURL}
DefaultDirName={autopf}\{#MyAppPublisher}\Imager
DefaultGroupName=Raspberry Pi
AllowNoIcons=yes
LicenseFile=@CMAKE_BINARY_DIR@\deploy\license.txt
OutputDir=@CMAKE_BINARY_DIR@\installer
OutputBaseFilename=imager-{#MyAppVersion}
SetupIconFile=@CMAKE_SOURCE_DIR@\icons\rpi-imager.ico
Compression=lzma2/max
SolidCompression=yes
WizardStyle=modern dynamic
ArchitecturesInstallIn64BitMode=x64compatible
MinVersion=10.0.15063
PrivilegesRequired=admin
UninstallDisplayIcon={app}\{#MyAppExeName},0
UninstallDisplayName={#MyAppName}
DisableProgramGroupPage=yes
DisableWelcomePage=no
DisableReadyPage=yes
DisableFinishedPage=no
CreateUninstallRegKey=yes
#ifdef SIGNING_ENABLED
SignTool=sign $f
SignedUninstaller=yes
#endif

[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "dutch"; MessagesFile: "compiler:Languages\Dutch.isl"
Name: "french"; MessagesFile: "compiler:Languages\French.isl"
Name: "german"; MessagesFile: "compiler:Languages\German.isl"
Name: "italian"; MessagesFile: "compiler:Languages\Italian.isl"
Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl"

[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked

[Registry]
Root: HKLM; Subkey: "Software\Microsoft\Windows\CurrentVersion\App Paths\{#MyAppExeName}"; ValueType: string; ValueName: ""; ValueData: "{app}\{#MyAppExeName}"; Flags: uninsdeletekey
Root: HKLM; Subkey: "Software\Classes\.zip\OpenWithProgIds"; ValueType: string; ValueName: "RPI_IMAGINGUTILITY"; ValueData: ""; Flags: uninsdeletevalue
Root: HKLM; Subkey: "Software\Classes\.gz\OpenWithProgIds"; ValueType: string; ValueName: "RPI_IMAGINGUTILITY"; ValueData: ""; Flags: uninsdeletevalue
Root: HKLM; Subkey: "Software\Classes\.xz\OpenWithProgIds"; ValueType: string; ValueName: "RPI_IMAGINGUTILITY"; ValueData: ""; Flags: uninsdeletevalue
Root: HKLM; Subkey: "Software\Classes\.img\OpenWithProgIds"; ValueType: string; ValueName: "RPI_IMAGINGUTILITY"; ValueData: ""; Flags: uninsdeletevalue
Root: HKLM; Subkey: "Software\Classes\.wic\OpenWithProgIds"; ValueType: string; ValueName: "RPI_IMAGINGUTILITY"; ValueData: ""; Flags: uninsdeletevalue
Root: HKLM; Subkey: "Software\Classes\.zstd\OpenWithProgIds"; ValueType: string; ValueName: "RPI_IMAGINGUTILITY"; ValueData: ""; Flags: uninsdeletevalue
Root: HKLM; Subkey: "Software\Classes\RPI_IMAGINGUTILITY\shell\open"; ValueType: string; ValueName: "FriendlyAppName"; ValueData: "{#MyAppName}"; Flags: uninsdeletekey
Root: HKLM; Subkey: "Software\Classes\RPI_IMAGINGUTILITY\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; Flags: uninsdeletekey

; Register .rpi-imager-manifest file type (repository manifest files)
; Extension defined in src/config.h (MANIFEST_EXTENSION)
Root: HKLM; Subkey: "Software\Classes\.rpi-imager-manifest"; ValueType: string; ValueName: ""; ValueData: "RPI_IMAGER_MANIFEST"; Flags: uninsdeletekey
Root: HKLM; Subkey: "Software\Classes\.rpi-imager-manifest"; ValueType: string; ValueName: "Content Type"; ValueData: "application/vnd.raspberrypi.imager-manifest+json"; Flags: uninsdeletekey
Root: HKLM; Subkey: "Software\Classes\RPI_IMAGER_MANIFEST"; ValueType: string; ValueName: ""; ValueData: "Raspberry Pi Imager Manifest"; Flags: uninsdeletekey
Root: HKLM; Subkey: "Software\Classes\RPI_IMAGER_MANIFEST\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#MyAppExeName},0"; Flags: uninsdeletekey
Root: HKLM; Subkey: "Software\Classes\RPI_IMAGER_MANIFEST\shell\open"; ValueType: string; ValueName: "FriendlyAppName"; ValueData: "{#MyAppName}"; Flags: uninsdeletekey
Root: HKLM; Subkey: "Software\Classes\RPI_IMAGER_MANIFEST\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; Flags: uninsdeletekey

; Register rpi-imager:// URL protocol handler
Root: HKCR; Subkey: "rpi-imager"; ValueType: string; ValueName: ""; ValueData: "URL:Raspberry Pi Imager"; Flags: uninsdeletekey
Root: HKCR; Subkey: "rpi-imager"; ValueType: string; ValueName: "URL Protocol"; ValueData: ""; Flags: uninsdeletekey
Root: HKCR; Subkey: "rpi-imager\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#MyAppExeName},1"; Flags: uninsdeletekey
Root: HKCR; Subkey: "rpi-imager\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\rpi-imager-callback-relay.exe"" ""%1"""; Flags: uninsdeletekey

[Files]
; WinUSB driver for Raspberry Pi USB boot mode devices (rpiboot).
; For signed builds the INF has CatalogFile= uncommented and the matching
; signed .cat is included; for unsigned builds only the bare INF is present.
Source: "@CMAKE_BINARY_DIR@\deploy\rpiboot-winusb.inf"; DestDir: "{app}"; Flags: ignoreversion
Source: "@CMAKE_BINARY_DIR@\deploy\rpiboot-winusb.cat"; DestDir: "{app}"; Flags: ignoreversion skipifsourcedoesntexist

; Main program files
Source: "@CMAKE_BINARY_DIR@\deploy\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion signonce

Source: "@CMAKE_BINARY_DIR@\deploy\rpi-imager-cli.cmd"; DestDir: "{app}"; Flags: ignoreversion
Source: "@CMAKE_BINARY_DIR@\deploy\rpi-imager-callback-relay.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "@CMAKE_BINARY_DIR@\deploy\license.txt"; DestDir: "{app}"; Flags: ignoreversion

; Core DLLs
Source: "@CMAKE_BINARY_DIR@\deploy\D3Dcompiler_47.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "@CMAKE_BINARY_DIR@\deploy\libgcc_s_seh-1.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "@CMAKE_BINARY_DIR@\deploy\libstdc++-6.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "@CMAKE_BINARY_DIR@\deploy\libwinpthread-1.dll"; DestDir: "{app}"; Flags: ignoreversion

; Qt DLLs
Source: "@CMAKE_BINARY_DIR@\deploy\Qt6*.dll"; DestDir: "{app}"; Flags: ignoreversion

; Qt Plugin directories
Source: "@CMAKE_BINARY_DIR@\deploy\generic\*"; DestDir: "{app}\generic"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "@CMAKE_BINARY_DIR@\deploy\iconengines\*"; DestDir: "{app}\iconengines"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "@CMAKE_BINARY_DIR@\deploy\imageformats\*"; DestDir: "{app}\imageformats"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "@CMAKE_BINARY_DIR@\deploy\networkinformation\*"; DestDir: "{app}\networkinformation"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "@CMAKE_BINARY_DIR@\deploy\platforminputcontexts\*"; DestDir: "{app}\platforminputcontexts"; Flags: ignoreversion recursesubdirs createallsubdirs skipifsourcedoesntexist
Source: "@CMAKE_BINARY_DIR@\deploy\platforms\*"; DestDir: "{app}\platforms"; Flags: ignoreversion recursesubdirs createallsubdirs
; Source: "@CMAKE_BINARY_DIR@\deploy\styles\*"; DestDir: "{app}\styles"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "@CMAKE_BINARY_DIR@\deploy\tls\*"; DestDir: "{app}\tls"; Flags: ignoreversion recursesubdirs createallsubdirs

; QML directories
Source: "@CMAKE_BINARY_DIR@\deploy\qml\*"; DestDir: "{app}\qml"; Flags: ignoreversion recursesubdirs createallsubdirs

[Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; AfterInstall: SetElevationBit('{group}\{#MyAppName}.lnk')
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon; AfterInstall: SetElevationBit('{commondesktop}\{#MyAppName}.lnk')

[Run]
; Stage and install WinUSB driver for Raspberry Pi USB boot mode devices.
; pnputil /install also applies the driver to any currently-connected device.
; Return code 3010 means success-with-reboot-required and is treated as success.
Filename: "{sys}\pnputil.exe"; Parameters: "/add-driver ""{app}\rpiboot-winusb.inf"" /install"; Flags: runhidden waituntilterminated; StatusMsg: "Installing USB driver for Raspberry Pi boot mode..."

Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent runascurrentuser

[InstallDelete]
; 64-bit Windows – old NSIS path lives in Program Files (x86)
Type: filesandordirs; Name: "{commonpf32}\Raspberry Pi Imager\qml"; Check: IsWin64
Type: dirifempty;      Name: "{commonpf32}\Raspberry Pi Imager";   Check: IsWin64

; 32-bit Windows – old NSIS path lives in Program Files
Type: filesandordirs; Name: "{commonpf}\Raspberry Pi Imager\qml";  Check: not IsWin64
Type: dirifempty;      Name: "{commonpf}\Raspberry Pi Imager";     Check: not IsWin64

[Code]

// Inno doesn't support setting 'Run as Administrator' on icons natively
// https://stackoverflow.com/a/44082068
procedure SetElevationBit(Filename: string);
var
  Buffer: string;
  Stream: TStream;
begin
  Filename := ExpandConstant(Filename);
  Log('Setting elevation bit for ' + Filename);

  Stream := TFileStream.Create(FileName, fmOpenReadWrite);
  try
    Stream.Seek(21, soFromBeginning);
    SetLength(Buffer, 1);
    Stream.ReadBuffer(Buffer, 1);
    Buffer[1] := Chr(Ord(Buffer[1]) or $20);
    Stream.Seek(-1, soFromCurrent);
    Stream.WriteBuffer(Buffer, 1);
  finally
    Stream.Free;
  end;
end;

// Helper function to find a process by name
function FindProcess(ExeFileName: String; var ProcessID: Integer): Boolean;
var
  CmdLine: String;
  ResultCode: Integer;
  OutputFile: String;
  FileLines: TArrayOfString;
  I: Integer;
  ProcessLine: String;
  IdStr: String;
begin
  Result := False;
  OutputFile := ExpandConstant('{tmp}\processes.txt');
  
  // Use tasklist to get running processes
  CmdLine := Format('/c tasklist /FI "IMAGENAME eq %s" /FO CSV /NH > "%s"', [ExeFileName, OutputFile]);
  ShellExec('', 'cmd.exe', CmdLine, '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
  
  if ResultCode = 0 then
  begin
    // Read the output file
    if LoadStringsFromFile(OutputFile, FileLines) then
    begin
      for I := 0 to GetArrayLength(FileLines) - 1 do
      begin
        ProcessLine := FileLines[I];
        // Process line is in CSV format: "imagename","PID",...
        if Pos('"' + ExeFileName + '"', ProcessLine) = 1 then
        begin
          // Get the PID
          ProcessLine := Copy(ProcessLine, Length(ExeFileName) + 3, Length(ProcessLine));
          IdStr := '';
          
          // Extract numeric PID
          while (Length(ProcessLine) > 0) and (ProcessLine[1] <> '"') do
          begin
            ProcessLine := Copy(ProcessLine, 2, Length(ProcessLine));
          end;
          
          if Length(ProcessLine) > 0 then 
          begin
            ProcessLine := Copy(ProcessLine, 2, Length(ProcessLine)); // Skip "
            while (Length(ProcessLine) > 0) and (ProcessLine[1] <> '"') do
            begin
              IdStr := IdStr + ProcessLine[1];
              ProcessLine := Copy(ProcessLine, 2, Length(ProcessLine));
            end;
            
            ProcessID := StrToIntDef(IdStr, 0);
            if ProcessID > 0 then
            begin
              Result := True;
              break;
            end;
          end;
        end;
      end;
    end;
    
    // Clean up temporary file
    DeleteFile(OutputFile);
  end;
end;

// Function to check if Windows 10 or later
function IsWindows10OrNewer: Boolean;
var
  Version: TWindowsVersion;
begin
  GetWindowsVersionEx(Version);
  Result := (Version.Major >= 10);
end; 

// Function to check if a program is installed by the NSIS installer
function IsNSISVersionInstalled: Boolean;
var
  NSISUninstallString: String;
begin
  Result := False;
  
  // Check for NSIS registry entry in HKCU
  if RegQueryStringValue(HKCU, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\Raspberry Pi Imager',
                          'UninstallString', NSISUninstallString) then
  begin
    Result := True;
  end;
  
  // Also check in HKLM
  if not Result and RegQueryStringValue(HKLM, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\Raspberry Pi Imager',
                                       'UninstallString', NSISUninstallString) then
  begin
    Result := True;
  end;
end;

const
  NsisKey = 'Software\Microsoft\Windows\CurrentVersion\Uninstall\Raspberry Pi Imager';
  OldInnoAppIdPart = '{6D809377-6AF0-444B-8957-A3773F02200E}'; // old Inno AppId part (no _is1)

function SplitExeAndArgs(const Line: string; var Exe, Params: string): Boolean;
var
  L: string; p: Integer;
begin
  L := Trim(Line);
  Exe := ''; Params := '';

  // Quoted path?
  if (L <> '') and (L[1] = '"') then
  begin
    Delete(L, 1, 1);
    p := Pos('"', L);
    if p > 0 then
    begin
      Exe := Copy(L, 1, p-1);
      Params := Trim(Copy(L, p+1, MaxInt));
      Result := (Exe <> '');
      Exit;
    end;
  end;

  // Unquoted – split at .exe
  p := Pos(LowerCase('.exe'), LowerCase(L));
  if p > 0 then
  begin
    p := p + 3; // index at 'e' in .exe; +1 more below to include 'e'
    Exe := Trim(Copy(L, 1, p+1));              // include full ".exe"
    Params := Trim(Copy(L, p+2, MaxInt));      // rest (may be empty)
    Result := (Exe <> '');
    Exit;
  end;

  // Last resort: split on first space
  p := Pos(' ', L);
  if p > 0 then
  begin
    Exe := Copy(L, 1, p-1);
    Params := Trim(Copy(L, p+1, MaxInt));
  end
  else
    Exe := L;

  Result := (Exe <> '');
end;

// Old Inno (AppId-based)
function TryGetOldInnoUninstall(var Cmd, Args: string): Boolean;
var
  Roots: array[0..5] of Integer;
  Keys: TArrayOfString;
  i, r: Integer;
  SubKey, KeyName, S, QUS, US: string;
begin
  Result := False;
  Roots[0] := HKLM;   Roots[1] := HKLM32; Roots[2] := HKLM64;
  Roots[3] := HKCU;   Roots[4] := HKCU32; Roots[5] := HKCU64;

  for r := 0 to 5 do begin
    if RegGetSubkeyNames(Roots[r], 'Software\Microsoft\Windows\CurrentVersion\Uninstall', Keys) then
    begin
      for i := 0 to GetArrayLength(Keys)-1 do begin
        KeyName := Keys[i];
        if (Pos(OldInnoAppIdPart, KeyName) = 1) and (Pos('_is1', KeyName) > 0) then begin
          SubKey := 'Software\Microsoft\Windows\CurrentVersion\Uninstall\' + KeyName;
          RegQueryStringValue(Roots[r], SubKey, 'QuietUninstallString', QUS);
          RegQueryStringValue(Roots[r], SubKey, 'UninstallString', US);
          S := QUS; if S = '' then S := US;
          if (S <> '') and SplitExeAndArgs(S, Cmd, Args) then begin
            if (QUS = '') then begin
              if Args <> '' then Args := Args + ' ';
              Args := Args + '/S'; // silent fallback
            end;
            Result := True; Exit;
          end;
        end;
      end;
    end;
  end;
end;

// Old NSIS (name-based key)
function TryGetOldNsisUninstall(var Cmd, Args: string): Boolean;
var
  Roots: array[0..5] of Integer;
  r: Integer;
  QUS, US: string;
begin
  Result := False;
  Roots[0] := HKLM;   Roots[1] := HKLM32; Roots[2] := HKLM64;
  Roots[3] := HKCU;   Roots[4] := HKCU32; Roots[5] := HKCU64;

  for r := 0 to 5 do begin
    if RegQueryStringValue(Roots[r], NsisKey, 'QuietUninstallString', QUS) or
       RegQueryStringValue(Roots[r], NsisKey, 'UninstallString', US) then
    begin
      if (QUS <> '') and SplitExeAndArgs(QUS, Cmd, Args) then begin
        Result := True; Exit;
      end else
      if (US <> '') and SplitExeAndArgs(US, Cmd, Args) then begin
        if Args <> '' then Args := Args + ' ';
        Args := Args + '/S'; // silent fallback
        Result := True; Exit;
      end;
    end;
  end;
end;

// Forcibly remove old installation directories
// Known locations from previous installers:
//   - NSIS (32-bit): {commonpf32}\Raspberry Pi Imager
//   - NSIS (64-bit): {commonpf}\Raspberry Pi Imager  
//   - Old Inno:      {autopf}\Raspberry Pi Ltd\Imager
procedure CleanupOldInstallDirs();
var
  OldDirs: array[0..2] of string;
  i: Integer;
begin
  OldDirs[0] := ExpandConstant('{commonpf32}\Raspberry Pi Imager');
  OldDirs[1] := ExpandConstant('{commonpf}\Raspberry Pi Imager');
  OldDirs[2] := ExpandConstant('{autopf}\Raspberry Pi Ltd\Imager');

  for i := 0 to 2 do begin
    if DirExists(OldDirs[i]) then begin
      Log('Removing old installation directory: ' + OldDirs[i]);
      DelTree(OldDirs[i], True, True, True);
    end;
  end;
  
  // Also clean up the parent directory if empty
  if DirExists(ExpandConstant('{autopf}\Raspberry Pi Ltd')) then begin
    RemoveDir(ExpandConstant('{autopf}\Raspberry Pi Ltd')); // only removes if empty
  end;
end;

// Remove old registry entries left behind by previous installers
procedure CleanupOldRegistryEntries();
var
  Roots: array[0..5] of Integer;
  Keys: TArrayOfString;
  i, r: Integer;
  KeyName, SubKey: string;
begin
  Roots[0] := HKLM;   Roots[1] := HKLM32; Roots[2] := HKLM64;
  Roots[3] := HKCU;   Roots[4] := HKCU32; Roots[5] := HKCU64;

  for r := 0 to 5 do begin
    // Remove NSIS registry key
    if RegKeyExists(Roots[r], NsisKey) then begin
      Log('Removing old NSIS registry key');
      RegDeleteKeyIncludingSubkeys(Roots[r], NsisKey);
    end;

    // Remove old Inno registry keys (by old AppId)
    if RegGetSubkeyNames(Roots[r], 'Software\Microsoft\Windows\CurrentVersion\Uninstall', Keys) then begin
      for i := 0 to GetArrayLength(Keys)-1 do begin
        KeyName := Keys[i];
        if (Pos(OldInnoAppIdPart, KeyName) = 1) and (Pos('_is1', KeyName) > 0) then begin
          SubKey := 'Software\Microsoft\Windows\CurrentVersion\Uninstall\' + KeyName;
          Log('Removing old Inno registry key: ' + SubKey);
          RegDeleteKeyIncludingSubkeys(Roots[r], SubKey);
        end;
      end;
    end;
  end;
end;

function RemovePreviousIfAny(): Boolean;
var
  Cmd, Args: string;
  RC: Integer;
  UninstallAttempted: Boolean;
begin
  Result := True;
  UninstallAttempted := False;

  // Old Inno (by old AppId)
  if TryGetOldInnoUninstall(Cmd, Args) then begin
    if MsgBox('A previous Raspberry Pi Imager installation was detected. It will be removed before continuing. Proceed?',
              mbConfirmation, MB_YESNO) <> IDYES then
    begin Result := False; Exit; end;

    UninstallAttempted := True;
    Exec(Cmd, Args, '', SW_HIDE, ewWaitUntilTerminated, RC);
    // Ignore return code - we'll clean up manually regardless
    Log('Old Inno uninstaller returned: ' + IntToStr(RC));
  end
  // Old NSIS (name-based)
  else if TryGetOldNsisUninstall(Cmd, Args) then begin
    if MsgBox('A previous Raspberry Pi Imager installation was detected. It will be removed before continuing. Proceed?',
              mbConfirmation, MB_YESNO) <> IDYES then
    begin Result := False; Exit; end;

    UninstallAttempted := True;
    Exec(Cmd, Args, '', SW_HIDE, ewWaitUntilTerminated, RC);
    // Ignore return code - we'll clean up manually regardless  
    Log('Old NSIS uninstaller returned: ' + IntToStr(RC));
  end;

  // Clean up any leftovers regardless of whether uninstall succeeded
  if UninstallAttempted then begin
    Log('Cleaning up old installation directories and registry entries...');
    CleanupOldInstallDirs();
    CleanupOldRegistryEntries();
  end;
end;

// Remove the WinUSB driver package staged by pnputil during install.
// pnputil renames our INF to an oem*.inf in the driver store; we must
// enumerate drivers to recover that published name before we can delete it.
procedure UninstallWinUSBDriver();
var
  TmpFile: string;
  Lines: TArrayOfString;
  I: Integer;
  CurrentOem, OemName: string;
  ResultCode: Integer;
begin
  TmpFile := ExpandConstant('{tmp}\pnputil_enum.txt');

  // Dump all staged third-party drivers to a temp file
  if not Exec(ExpandConstant('{sys}\cmd.exe'),
              '/c pnputil /enum-drivers > "' + TmpFile + '" 2>&1',
              '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
  begin
    Log('WinUSB driver uninstall: could not run pnputil /enum-drivers');
    Exit;
  end;

  if not LoadStringsFromFile(TmpFile, Lines) then
  begin
    Log('WinUSB driver uninstall: could not read pnputil output');
    Exit;
  end;
  DeleteFile(TmpFile);

  // pnputil output format (Win10+):
  //   Published Name:  oem5.inf
  //   Original Name:   rpiboot-winusb.inf
  //   ...
  // Track the most-recently-seen Published Name as we scan for our Original Name.
  OemName := '';
  CurrentOem := '';
  for I := 0 to GetArrayLength(Lines) - 1 do
  begin
    if Pos('published name', LowerCase(Lines[I])) > 0 then
      CurrentOem := Trim(Copy(Lines[I], Pos(':', Lines[I]) + 1, MaxInt))
    else if (Pos('original name', LowerCase(Lines[I])) > 0) and
            (Pos('rpiboot-winusb.inf', LowerCase(Lines[I])) > 0) then
    begin
      OemName := CurrentOem;
      Break;
    end;
  end;

  if OemName <> '' then
  begin
    Log('WinUSB driver uninstall: removing ' + OemName);
    Exec(ExpandConstant('{sys}\pnputil.exe'),
         '/delete-driver "' + OemName + '" /uninstall',
         '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
    Log('pnputil /delete-driver returned ' + IntToStr(ResultCode));
  end
  else
    Log('WinUSB driver uninstall: rpiboot-winusb.inf not found in driver store');
end;

procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
var
  CacheFile, CacheDir: string;
begin
  if CurUninstallStep = usUninstall then begin
    UninstallWinUSBDriver();
  end;

  if CurUninstallStep = usPostUninstall then begin
    // Delete the cached OS download (potentially several GB). Path is read
    // from QSettings at HKCU\Software\Raspberry Pi\Raspberry Pi Imager\caching\lastFileName.
    // The basename check guards against a tampered HKCU value being used to
    // point this admin-elevated uninstaller at an unrelated file.
    if RegQueryStringValue(HKCU, 'Software\Raspberry Pi\Raspberry Pi Imager\caching',
                           'lastFileName', CacheFile) then
    begin
      if (CacheFile <> '') and
         (CompareText(ExtractFileName(CacheFile), 'lastdownload.cache') = 0) and
         FileExists(CacheFile) then
      begin
        Log('Removing cached OS download: ' + CacheFile);
        DeleteFile(CacheFile);

        CacheDir := ExtractFileDir(CacheFile);
        if (CacheDir <> '') and DirExists(CacheDir) then
          RemoveDir(CacheDir); // no-op unless empty
      end;
    end;

    // Delete the saved image-customisation presets. These persist Wi-Fi PSK
    // (PBKDF2 hash, usable as the live wpa_supplicant credential), the Linux
    // user account password hash, and SSH keys; leaving them in HKCU after
    // uninstall is a privacy issue.
    if RegKeyExists(HKCU, 'Software\Raspberry Pi\Raspberry Pi Imager\imagecustomization') then begin
      Log('Removing saved image customisation settings (Wi-Fi/user/SSH credentials)');
      RegDeleteKeyIncludingSubkeys(HKCU, 'Software\Raspberry Pi\Raspberry Pi Imager\imagecustomization');
    end;

    // Other per-user QSettings (window position, language, telemetry choice,
    // lastpath, etc.) are intentionally preserved so reinstalling restores
    // user preferences.
  end;
end;

function InitializeSetup(): Boolean;
var
  rc: Integer;
  running: Boolean;
  pid: Integer;
begin
  // make sure app not running
  running := FindProcess('rpi-imager.exe', pid);
  if running then begin
    if MsgBox('Raspberry Pi Imager is running and needs to be closed before continuing. Close it now?',
              mbConfirmation, MB_YESNO) <> IDYES then
    begin Result := False; Exit; end;
    
    ShellExec('', 'taskkill.exe', '/IM rpi-imager.exe /F', '', SW_HIDE, ewWaitUntilTerminated, rc);
    // taskkill may fail if process exited between detection and kill attempt (race condition)
    // Re-check if process is actually still running
    Sleep(500); // Give process time to fully terminate
    if FindProcess('rpi-imager.exe', pid) then begin
      MsgBox('Failed to close Raspberry Pi Imager. Please close it manually and try again.', mbError, MB_OK);
      Result := False; Exit;
    end;
  end;

  // OS check (your IsWindows10OrNewer helper)
  if not IsWindows10OrNewer then begin
    MsgBox('This application requires Windows 10 or later.', mbError, MB_OK);
    Result := False; Exit;
  end;

  // uninstall old, if present (Inno-old first, else NSIS)
  if not RemovePreviousIfAny() then begin
    Result := False; Exit;
  end;

  Result := True;
end;
