Files
aza/AzA march 2026/installer/aza_installer.iss
2026-05-23 21:31:34 +02:00

312 lines
12 KiB
Plaintext

#define MyAppName "AzA"
#ifndef MyAppVersion
#define MyAppVersion "0.1.0"
#endif
#ifndef MyOutputBaseFilename
#define MyOutputBaseFilename "aza_desktop_setup"
#endif
#define MyAppPublisher "AZA MedWork"
#define MyAppExeName "aza_desktop.exe"
#define MyStartPanelExe "aza_start_panel.exe"
#define MyUpdaterExe "aza_updater.exe"
#define MyChatExe "AZA_EmpfangShell.exe"
#define MyAppSourceDir SourcePath + "\..\dist\aza_desktop"
[Setup]
AppId={{B7E4C0D2-6B5D-4D39-9D7C-5B0D5E8C2A11}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL=https://aza-medwork.ch
DefaultDirName={autopf}\AzA Medwork\AzA
DefaultGroupName=AzA
DisableProgramGroupPage=no
OutputDir={#MyAppSourceDir}\..\..\dist\installer
OutputBaseFilename={#MyOutputBaseFilename}
Compression=lzma
SolidCompression=yes
WizardStyle=modern
PrivilegesRequired=admin
ArchitecturesInstallIn64BitMode=x64
UninstallDisplayIcon={app}\logo.ico
SetupIconFile={#MyAppSourceDir}\..\..\logo.ico
SetupMutex=AZADesktopSetupMutex
AlwaysRestart=no
RestartIfNeededByRun=no
CloseApplications=yes
RestartApplications=no
SetupLogging=yes
[Languages]
Name: "german"; MessagesFile: "compiler:Languages\German.isl"
[Messages]
german.FinishedRestartMessage=Diese Installation kann erst nach einem Neustart abgeschlossen werden.%n%nBitte starten Sie den Computer neu und fuehren Sie danach die Installationsdatei erneut aus.%n%nEs erfolgt keine automatische Fortsetzung nach dem Neustart.%n%nMoechten Sie jetzt neu starten?
german.FinishedRestartLabel=Neustart noetig: danach Installer erneut ausfuehren
german.UninstalledAndNeedsRestart=Um die Deinstallation von %1 abzuschliessen, ist ein Neustart von Windows noetig.%n%nBitte starten Sie den Computer neu.%n%nEs erfolgt keine automatische Fortsetzung.%nFalls Sie AzA neu installieren, fuehren Sie danach die Installationsdatei erneut aus.%n%nMoechten Sie jetzt neu starten?
[Types]
Name: "standard"; Description: "AzA Standardinstallation (empfohlen)"
Name: "chatonly"; Description: "Nur AzA PraxisChat"
Name: "custom"; Description: "Benutzerdefiniert"; Flags: iscustom
[Components]
Name: "office"; Description: "AzA Office — Hauptprogramm (Dokumentation)"; Types: standard custom; Flags: checkablealone
Name: "chat"; Description: "AzA PraxisChat"; Types: standard chatonly custom; Flags: checkablealone
[Tasks]
Name: "desktopicon"; Description: "Desktop-Verknuepfung: AzA"; GroupDescription: "Verknuepfungen:"; Flags: checkedonce
[Dirs]
Name: "{app}\config"; Components: office; Permissions: users-modify
[InstallDelete]
Type: filesandordirs; Name: "{app}\_internal\*.pyd"
Type: filesandordirs; Name: "{app}\_internal\*.dll"
Type: files; Name: "{app}\_internal\*.tmp"
[Files]
; Ohne restartreplace/uninsrestartdelete: keine stille PendMove-/Neustart-Sackgasse; Dateien werden ersetzt, sobald die AzA-Prozesse freigegeben haben.
; --- Immer installiert: Startpanel, Updater, Assets, Version ---
Source: "{#MyAppSourceDir}\{#MyStartPanelExe}"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#MyAppSourceDir}\{#MyUpdaterExe}"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#MyAppSourceDir}\assets\*"; DestDir: "{app}\assets"; Flags: ignoreversion recursesubdirs createallsubdirs skipifsourcedoesntexist
Source: "{#MyAppSourceDir}\version.json"; DestDir: "{app}"; Flags: ignoreversion skipifsourcedoesntexist
Source: "{#MyAppSourceDir}\..\..\logo.ico"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#MyAppSourceDir}\..\..\logo.png"; DestDir: "{app}"; Flags: ignoreversion skipifsourcedoesntexist
; --- AzA Office (optional) ---
Source: "{#MyAppSourceDir}\{#MyAppExeName}"; DestDir: "{app}"; Components: office; Flags: ignoreversion
Source: "{#MyAppSourceDir}\_internal\*"; DestDir: "{app}\_internal"; Components: office; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "{#MyAppSourceDir}\_internal\backend_url.txt"; DestDir: "{app}"; Components: office; Flags: ignoreversion skipifsourcedoesntexist
Source: "{#MyAppSourceDir}\_internal\backend_token.txt"; DestDir: "{app}"; Components: office; Flags: ignoreversion skipifsourcedoesntexist
Source: "{#MyAppSourceDir}\BUILD_INFO.txt"; DestDir: "{app}"; Components: office; Flags: ignoreversion skipifsourcedoesntexist
Source: "{#MyAppSourceDir}\..\..\setup_openai_runtime.ps1"; DestDir: "{app}"; Components: office; Flags: ignoreversion
; --- AzA PraxisChat (optional) ---
Source: "{#MyAppSourceDir}\{#MyChatExe}"; DestDir: "{app}"; Components: chat; Flags: ignoreversion
[Icons]
Name: "{group}\AzA"; Filename: "{app}\{#MyStartPanelExe}"; IconFilename: "{app}\logo.ico"
Name: "{autodesktop}\AzA"; Filename: "{app}\{#MyStartPanelExe}"; IconFilename: "{app}\logo.ico"; Tasks: desktopicon
Name: "{group}\AzA Office"; Filename: "{app}\{#MyAppExeName}"; IconFilename: "{app}\logo.ico"; Components: office
Name: "{group}\OpenAI Schluessel einrichten"; Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\setup_openai_runtime.ps1"""; Components: office
Name: "{group}\AzA PraxisChat"; Filename: "{app}\{#MyChatExe}"; IconFilename: "{app}\logo.ico"; Components: chat
[Run]
Filename: "{app}\{#MyStartPanelExe}"; Description: "AzA oeffnen"; Flags: nowait postinstall skipifsilent
[UninstallRun]
Filename: "netsh.exe"; Parameters: "advfirewall firewall delete rule name=""AZA Desktop - Lokale Kommunikation"""; Flags: runhidden
[Code]
const
SW_RESTORE = 9;
ASFW_ANY = $FFFFFFFF;
HWND_TOPMOST = -1;
HWND_NOTOPMOST = -2;
SWP_NOSIZE = $0001;
SWP_NOMOVE = $0002;
SWP_SHOWWINDOW = $0040;
function SetForegroundWindow(hWnd: HWND): BOOL;
external 'SetForegroundWindow@user32.dll stdcall';
function GetForegroundWindow(): HWND;
external 'GetForegroundWindow@user32.dll stdcall';
function GetWindowThreadProcessId(hWnd: HWND; var lpdwProcessId: DWORD): DWORD;
external 'GetWindowThreadProcessId@user32.dll stdcall';
function AttachThreadInput(idAttach, idAttachTo: DWORD; fAttach: BOOL): BOOL;
external 'AttachThreadInput@user32.dll stdcall';
function BringWindowToTop(hWnd: HWND): BOOL;
external 'BringWindowToTop@user32.dll stdcall';
function ShowWindow(hWnd: HWND; nCmdShow: Integer): BOOL;
external 'ShowWindow@user32.dll stdcall';
function SetWindowPos(hWnd, hWndInsertAfter: HWND; X, Y, cx, cy: Integer; uFlags: UINT): BOOL;
external 'SetWindowPos@user32.dll stdcall';
function AllowSetForegroundWindow(dwProcessId: DWORD): BOOL;
external 'AllowSetForegroundWindow@user32.dll stdcall';
function FlashWindow(hWnd: HWND; bInvert: BOOL): BOOL;
external 'FlashWindow@user32.dll stdcall';
function GetCurrentProcessId(): DWORD;
external 'GetCurrentProcessId@kernel32.dll stdcall';
function IsAzaProcessRunning(const ExeName: String): Boolean;
var
R: Integer;
begin
Result := False;
if Exec('cmd.exe', '/C tasklist /FI "IMAGENAME eq ' + ExeName + '" /NH | find /I "' + ExeName + '"', '', SW_HIDE, ewWaitUntilTerminated, R) then
Result := (R = 0);
end;
procedure CloseAzaProcesses();
var
R, Attempt: Integer;
begin
for Attempt := 1 to 3 do
begin
Exec('taskkill.exe', '/IM aza_start_panel.exe', '', SW_HIDE, ewWaitUntilTerminated, R);
Exec('taskkill.exe', '/IM aza_desktop.exe', '', SW_HIDE, ewWaitUntilTerminated, R);
Exec('taskkill.exe', '/IM aza_updater.exe', '', SW_HIDE, ewWaitUntilTerminated, R);
Exec('taskkill.exe', '/IM AZA_EmpfangShell.exe', '', SW_HIDE, ewWaitUntilTerminated, R);
Sleep(400);
Exec('taskkill.exe', '/F /T /IM aza_start_panel.exe', '', SW_HIDE, ewWaitUntilTerminated, R);
Exec('taskkill.exe', '/F /T /IM aza_desktop.exe', '', SW_HIDE, ewWaitUntilTerminated, R);
Exec('taskkill.exe', '/F /T /IM aza_updater.exe', '', SW_HIDE, ewWaitUntilTerminated, R);
Exec('taskkill.exe', '/F /T /IM AZA_EmpfangShell.exe', '', SW_HIDE, ewWaitUntilTerminated, R);
Sleep(800);
if not IsAzaProcessRunning('aza_start_panel.exe') and
not IsAzaProcessRunning('aza_desktop.exe') and
not IsAzaProcessRunning('aza_updater.exe') and
not IsAzaProcessRunning('AZA_EmpfangShell.exe') then
Break;
end;
end;
function AnyAzaProcessRunning(): Boolean;
begin
Result := IsAzaProcessRunning('aza_start_panel.exe') or
IsAzaProcessRunning('aza_desktop.exe') or
IsAzaProcessRunning('aza_updater.exe') or
IsAzaProcessRunning('AZA_EmpfangShell.exe');
end;
procedure ForceSetupForeground();
var
Wnd: HWND;
ForegroundThread, TargetThread, DummyPid: DWORD;
begin
try
AllowSetForegroundWindow(ASFW_ANY);
AllowSetForegroundWindow(GetCurrentProcessId());
except
end;
try
WizardForm.FormStyle := fsStayOnTop;
WizardForm.Show;
except
end;
Wnd := WizardForm.Handle;
if Wnd = 0 then
Exit;
ForegroundThread := GetWindowThreadProcessId(GetForegroundWindow(), DummyPid);
TargetThread := GetWindowThreadProcessId(Wnd, DummyPid);
if (ForegroundThread <> 0) and (TargetThread <> 0) and (ForegroundThread <> TargetThread) then
begin
AttachThreadInput(ForegroundThread, TargetThread, True);
SetForegroundWindow(Wnd);
AttachThreadInput(ForegroundThread, TargetThread, False);
end
else
SetForegroundWindow(Wnd);
ShowWindow(Wnd, SW_RESTORE);
BringWindowToTop(Wnd);
{ Kurz TOPMOST, danach wieder normal — erzwingt Z-Reihenfolge }
SetWindowPos(Wnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE or SWP_NOSIZE or SWP_SHOWWINDOW);
SetWindowPos(Wnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE or SWP_NOSIZE or SWP_SHOWWINDOW);
FlashWindow(Wnd, True);
end;
procedure InitializeWizard();
begin
ForceSetupForeground();
end;
procedure CurPageChanged(CurPageID: Integer);
begin
ForceSetupForeground();
end;
function NextButtonClick(CurPageID: Integer): Boolean;
begin
Result := True;
if CurPageID = wpSelectComponents then
begin
if not WizardIsComponentSelected('office') and not WizardIsComponentSelected('chat') then
begin
MsgBox('Bitte mindestens AzA Office und/oder AzA PraxisChat waehlen.', mbError, MB_OK);
Result := False;
Exit;
end;
if WizardIsComponentSelected('office') and not WizardIsComponentSelected('chat') then
begin
if MsgBox('Ohne AzA PraxisChat startet der Chat-Button in AzA nicht.' + #13#10 + #13#10 +
'Empfehlung: Aktivieren Sie AzA PraxisChat zusaetzlich zu AzA Office.' + #13#10 + #13#10 +
'Trotzdem fortfahren und nur AzA Office installieren?',
mbConfirmation, MB_YESNO or MB_DEFBUTTON2) = IDNO then
Result := False;
end;
end;
end;
function InitializeSetup(): Boolean;
begin
Result := True;
try
AllowSetForegroundWindow(ASFW_ANY);
AllowSetForegroundWindow(GetCurrentProcessId());
except
end;
end;
function InitializeUninstall(): Boolean;
begin
Result := True;
if not WizardSilent then
MsgBox('AzA wird fuer die Deinstallation kurz geschlossen.', mbInformation, MB_OK);
CloseAzaProcesses();
end;
function PrepareToInstall(var NeedsRestart: Boolean): String;
begin
Result := '';
NeedsRestart := False;
if not WizardSilent then
MsgBox('AzA wird fuer die Installation kurz geschlossen.', mbInformation, MB_OK);
CloseAzaProcesses();
if AnyAzaProcessRunning() then
begin
Result := 'AzA laeuft noch im Hintergrund.' + #13#10 + #13#10 +
'Bitte schliessen Sie alle AzA-Fenster (Startpanel, Office, PraxisChat, Updater) ' +
'und starten Sie die Installation erneut.' + #13#10 + #13#10 +
'Ein Neustart von Windows ist normalerweise nicht noetig.';
end;
end;
procedure CurStepChanged(CurStep: TSetupStep);
var
R: Integer;
AppPath, ExePath, Cmd: String;
begin
if CurStep = ssPostInstall then
begin
AppPath := ExpandConstant('{app}');
ExePath := AddBackslash(AppPath) + 'aza_desktop.exe';
if WizardIsComponentSelected('office') and FileExists(ExePath) then
begin
Exec('netsh.exe', 'advfirewall firewall delete rule name="AZA Desktop - Lokale Kommunikation"', '', SW_HIDE, ewWaitUntilTerminated, R);
Sleep(200);
Cmd := 'advfirewall firewall add rule name="AZA Desktop - Lokale Kommunikation" dir=in action=allow program="' + ExePath + '" localport=8000 protocol=tcp remoteip=127.0.0.1';
if not Exec('netsh.exe', Cmd, '', SW_HIDE, ewWaitUntilTerminated, R) or (R <> 0) then
Log('Firewall-Regel konnte nicht angelegt werden (Code: ' + IntToStr(R) + ')');
end;
end;
end;
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
var
UserDataDir: String;
begin
if CurUninstallStep = usUninstall then
CloseAzaProcesses();
if CurUninstallStep = usPostUninstall then
begin
UserDataDir := ExpandConstant('{userappdata}\AzA');
Log('AzA deinstalliert. Persoenliche Daten bleiben unter "' + UserDataDir + '" erhalten (kein automatisches Loeschen).');
end;
end;