From 4deb4b997ad7650bc4876f268be16e0c8170ec41 Mon Sep 17 00:00:00 2001 From: EvilBeaver Date: Thu, 8 Jan 2026 23:56:58 +0300 Subject: [PATCH 1/6] =?UTF-8?q?=D0=A8=D0=B0=D0=B3=20=D1=81=D0=B1=D0=BE?= =?UTF-8?q?=D1=80=D0=BA=D0=B8=20=D0=B8=D0=BD=D1=81=D1=82=D0=B0=D0=BB=D0=BB?= =?UTF-8?q?=D1=8F=D1=82=D0=BE=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release.yml | 22 +++++- install/ovm.iss | 142 ++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 install/ovm.iss diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f3e8303..ee1dc04 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,8 +27,26 @@ jobs: - name: Build ovm.exe run: oscript -make src/cmd/ovm.os ovm.exe - - name: Upload ovm.exe to release assets + - name: Extract version from packagedef + id: get_version + run: | + VERSION=$(grep -oP '\.Версия\("\K[^"]+' packagedef) + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Extracted version: $VERSION" + + - name: Create dist directory + run: mkdir -p dist + + - name: Build Windows installer with Inno Setup + run: | + docker run --rm -i -v "$GITHUB_WORKSPACE:/work" -w /work amake/innosetup \ + /DMyAppVersion="${{ steps.get_version.outputs.version }}" \ + install/ovm.iss + + - name: Upload release assets uses: AButler/upload-release-assets@v2.0.2 with: - files: ovm.exe + files: | + ovm.exe + dist/ovm-setup.exe repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/install/ovm.iss b/install/ovm.iss new file mode 100644 index 0000000..41129b3 --- /dev/null +++ b/install/ovm.iss @@ -0,0 +1,142 @@ +; Inno Setup script for OVM (OneScript Version Manager) +; Installs ovm.exe to %LOCALAPPDATA%\ovm and adds it to user PATH + +#define MyAppName "OneScript Version Manager" +; MyAppVersion is passed from GitHub Actions via /D flag, extracting from packagedef +; Default value is used for local builds +#ifndef MyAppVersion + #define MyAppVersion "1.0.0" +#endif +#define MyAppPublisher "oscript-library" +#define MyAppURL "https://github.com/oscript-library/ovm" +#define MyAppExeName "ovm.exe" + +[Setup] +; NOTE: The value of AppId uniquely identifies this application. +AppId={{8E5F4A2B-9C3D-4F1E-A6B8-2D7C9E4F1A3B} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +DefaultDirName={localappdata}\ovm +DisableProgramGroupPage=yes +OutputDir=..\dist +OutputBaseFilename=ovm-setup +Compression=lzma +SolidCompression=yes +; Per-user installation (no admin rights required) +PrivilegesRequired=lowest +PrivilegesRequiredOverridesAllowed=dialog +; Notify Windows about environment changes +ChangesEnvironment=yes +ArchitecturesInstallIn64BitMode=x64 +UninstallDisplayIcon={app}\{#MyAppExeName} + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" +Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl" + +[Files] +Source: "..\ovm.exe"; DestDir: "{app}"; Flags: ignoreversion + +[Code] +const + EnvironmentKey = 'Environment'; + +function NeedsAddPath(Param: string): boolean; +var + OrigPath: string; + ParamExpanded: string; +begin + ParamExpanded := ExpandConstant(Param); + if not RegQueryStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', OrigPath) then + begin + Result := True; + exit; + end; + // Check if our path already exists in PATH (case insensitive) + // Check without trailing backslash + Result := Pos(';' + Uppercase(ParamExpanded) + ';', ';' + Uppercase(OrigPath) + ';') = 0; + // Also check with trailing backslash variant + if Result = True then + Result := Pos(';' + Uppercase(ParamExpanded) + '\' + ';', ';' + Uppercase(OrigPath) + ';') = 0; +end; + +procedure CurStepChanged(CurStep: TSetupStep); +var + OrigPath: string; + NewPath: string; + AppPath: string; +begin + if CurStep = ssPostInstall then + begin + AppPath := ExpandConstant('{app}'); + if NeedsAddPath(AppPath) then + begin + if RegQueryStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', OrigPath) then + begin + // Remove trailing backslash if present + if (Length(OrigPath) > 0) and (OrigPath[Length(OrigPath)] = ';') then + NewPath := OrigPath + AppPath + else + NewPath := OrigPath + ';' + AppPath; + + if RegWriteStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', NewPath) then + Log('Added to PATH: ' + AppPath) + else + Log('Failed to add to PATH'); + end + else + begin + // PATH doesn't exist, create it + if RegWriteStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', AppPath) then + Log('Created PATH with: ' + AppPath) + else + Log('Failed to create PATH'); + end; + end + else + Log('Path already in PATH, skipping'); + end; +end; + +procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); +var + OrigPath: string; + NewPath: string; + AppPath: string; + PathList: TStringList; + i: Integer; +begin + if CurUninstallStep = usPostUninstall then + begin + AppPath := ExpandConstant('{app}'); + if RegQueryStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', OrigPath) then + begin + PathList := TStringList.Create; + try + PathList.Delimiter := ';'; + PathList.StrictDelimiter := True; + PathList.DelimitedText := OrigPath; + + // Remove our path from the list + for i := PathList.Count - 1 downto 0 do + begin + if Uppercase(PathList[i]) = Uppercase(AppPath) then + begin + PathList.Delete(i); + Log('Removed from PATH: ' + AppPath); + end; + end; + + NewPath := PathList.DelimitedText; + RegWriteStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', NewPath); + finally + PathList.Free; + end; + end; + end; +end; + From 2e5908d950de6fb5c29e5cf190142b0356a40bf0 Mon Sep 17 00:00:00 2001 From: EvilBeaver Date: Fri, 9 Jan 2026 12:58:11 +0300 Subject: [PATCH 2/6] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=B2=20=D1=81=D0=BA=D1=80=D0=B8=D0=BF=D1=82=D0=B5=20=D0=B8?= =?UTF-8?q?=D0=BD=D1=81=D1=82=D0=B0=D0=BB=D0=BB=D1=8F=D1=82=D0=BE=D1=80?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- install/ovm.iss | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/install/ovm.iss b/install/ovm.iss index 41129b3..4c204dc 100644 --- a/install/ovm.iss +++ b/install/ovm.iss @@ -28,7 +28,7 @@ Compression=lzma SolidCompression=yes ; Per-user installation (no admin rights required) PrivilegesRequired=lowest -PrivilegesRequiredOverridesAllowed=dialog +PrivilegesRequiredOverridesAllowed=commandline ; Notify Windows about environment changes ChangesEnvironment=yes ArchitecturesInstallIn64BitMode=x64 @@ -77,7 +77,7 @@ begin begin if RegQueryStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', OrigPath) then begin - // Remove trailing backslash if present + // Add semicolon separator if PATH doesn't already end with one if (Length(OrigPath) > 0) and (OrigPath[Length(OrigPath)] = ';') then NewPath := OrigPath + AppPath else @@ -108,6 +108,7 @@ var NewPath: string; AppPath: string; PathList: TStringList; + PathItem: string; i: Integer; begin if CurUninstallStep = usPostUninstall then @@ -121,13 +122,27 @@ begin PathList.StrictDelimiter := True; PathList.DelimitedText := OrigPath; - // Remove our path from the list + // Remove our path from the list (both with and without trailing backslash) + // Also remove empty entries to avoid ";;" in PATH for i := PathList.Count - 1 downto 0 do begin - if Uppercase(PathList[i]) = Uppercase(AppPath) then + PathItem := PathList[i]; + + // Remove empty entries + if Trim(PathItem) = '' then begin PathList.Delete(i); - Log('Removed from PATH: ' + AppPath); + Continue; + end; + + // Normalize by removing trailing backslash for comparison + if PathItem[Length(PathItem)] = '\' then + PathItem := Copy(PathItem, 1, Length(PathItem) - 1); + + if Uppercase(PathItem) = Uppercase(AppPath) then + begin + Log('Removed from PATH: ' + PathList[i]); + PathList.Delete(i); end; end; From 155d2b01347637fec060114e62428c090f4971ac Mon Sep 17 00:00:00 2001 From: EvilBeaver Date: Fri, 9 Jan 2026 13:02:36 +0300 Subject: [PATCH 3/6] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20Access=20denied=20=D0=BF=D1=80=D0=B8?= =?UTF-8?q?=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D0=B8=20=D0=B4?= =?UTF-8?q?=D0=B8=D1=81=D1=82=D1=80=D0=B8=D0=B1=D1=83=D1=82=D0=B8=D0=B2?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ee1dc04..f8c0994 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,7 +35,9 @@ jobs: echo "Extracted version: $VERSION" - name: Create dist directory - run: mkdir -p dist + run: | + mkdir -p dist + chmod 777 dist - name: Build Windows installer with Inno Setup run: | From e1e35dd4ab562519e75f4160385006d66d92182a Mon Sep 17 00:00:00 2001 From: EvilBeaver Date: Fri, 9 Jan 2026 13:19:35 +0300 Subject: [PATCH 4/6] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=87=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20copilot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- install/ovm.iss | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/install/ovm.iss b/install/ovm.iss index 4c204dc..bb80679 100644 --- a/install/ovm.iss +++ b/install/ovm.iss @@ -51,6 +51,10 @@ var ParamExpanded: string; begin ParamExpanded := ExpandConstant(Param); + // Normalize: remove trailing backslash + if (Length(ParamExpanded) > 0) and (ParamExpanded[Length(ParamExpanded)] = '\') then + ParamExpanded := Copy(ParamExpanded, 1, Length(ParamExpanded) - 1); + if not RegQueryStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', OrigPath) then begin Result := True; @@ -114,6 +118,10 @@ begin if CurUninstallStep = usPostUninstall then begin AppPath := ExpandConstant('{app}'); + // Normalize: remove trailing backslash + if (Length(AppPath) > 0) and (AppPath[Length(AppPath)] = '\') then + AppPath := Copy(AppPath, 1, Length(AppPath) - 1); + if RegQueryStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', OrigPath) then begin PathList := TStringList.Create; @@ -136,7 +144,7 @@ begin end; // Normalize by removing trailing backslash for comparison - if PathItem[Length(PathItem)] = '\' then + if (Length(PathItem) > 0) and (PathItem[Length(PathItem)] = '\') then PathItem := Copy(PathItem, 1, Length(PathItem) - 1); if Uppercase(PathItem) = Uppercase(AppPath) then From b4549435f8c7b81c91278b16f6738bc45216d6f8 Mon Sep 17 00:00:00 2001 From: Andrei Ovsiankin Date: Sat, 10 Jan 2026 14:11:11 +0300 Subject: [PATCH 5/6] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/release.yml | 12 ++++++++++-- install/ovm.iss | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f8c0994..90c3c30 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,8 +30,12 @@ jobs: - name: Extract version from packagedef id: get_version run: | - VERSION=$(grep -oP '\.Версия\("\K[^"]+' packagedef) - echo "version=$VERSION" >> $GITHUB_OUTPUT + VERSION=$(grep -oP '\.Версия\("\K[^"]+' packagedef || true) + if [ -z "$VERSION" ]; then + echo "Error: failed to extract version from packagedef." + exit 1 + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" echo "Extracted version: $VERSION" - name: Create dist directory @@ -41,6 +45,10 @@ jobs: - name: Build Windows installer with Inno Setup run: | + if [ ! -f "ovm.exe" ]; then + echo "Error: ovm.exe not found in workspace root. Ensure the 'Build ovm.exe' step completed successfully." + exit 1 + fi docker run --rm -i -v "$GITHUB_WORKSPACE:/work" -w /work amake/innosetup \ /DMyAppVersion="${{ steps.get_version.outputs.version }}" \ install/ovm.iss diff --git a/install/ovm.iss b/install/ovm.iss index bb80679..3f7c025 100644 --- a/install/ovm.iss +++ b/install/ovm.iss @@ -65,7 +65,7 @@ begin Result := Pos(';' + Uppercase(ParamExpanded) + ';', ';' + Uppercase(OrigPath) + ';') = 0; // Also check with trailing backslash variant if Result = True then - Result := Pos(';' + Uppercase(ParamExpanded) + '\' + ';', ';' + Uppercase(OrigPath) + ';') = 0; + Result := Pos(';' + Uppercase(ParamExpanded) + '\' + ';', ';' + Uppercase(OrigPath) + ';') = 0; end; procedure CurStepChanged(CurStep: TSetupStep); From b3d33df77aebe0f0096e1c637e0a72204d14849d Mon Sep 17 00:00:00 2001 From: EvilBeaver Date: Sat, 10 Jan 2026 19:12:40 +0300 Subject: [PATCH 6/6] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=BD=D0=B3=20=D0=B8=20=D1=84=D0=B8=D0=BA=D1=81?= =?UTF-8?q?=D1=8B=20=D0=98=D0=98-=D1=80=D0=B5=D0=B2=D1=8C=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- install/ovm.iss | 241 +++++++++++++++++++++++++++++++----------------- 1 file changed, 155 insertions(+), 86 deletions(-) diff --git a/install/ovm.iss b/install/ovm.iss index bb80679..cc73fbd 100644 --- a/install/ovm.iss +++ b/install/ovm.iss @@ -45,121 +45,190 @@ Source: "..\ovm.exe"; DestDir: "{app}"; Flags: ignoreversion const EnvironmentKey = 'Environment'; -function NeedsAddPath(Param: string): boolean; +// Удаляет завершающий обратный слеш из пути, если он есть +function NormalizePath(Path: string): string; +begin + Result := Path; + if (Length(Result) > 0) and (Result[Length(Result)] = '\') then + Result := Copy(Result, 1, Length(Result) - 1); +end; + +// Проверяет, существует ли путь в переменной PATH +// Учитывает варианты с завершающим слешем и без него +function PathExistsInEnv(Path: string): Boolean; var - OrigPath: string; - ParamExpanded: string; + EnvPath: string; + NormalizedPath: string; + SearchIn: string; begin - ParamExpanded := ExpandConstant(Param); - // Normalize: remove trailing backslash - if (Length(ParamExpanded) > 0) and (ParamExpanded[Length(ParamExpanded)] = '\') then - ParamExpanded := Copy(ParamExpanded, 1, Length(ParamExpanded) - 1); + Result := False; + + // Нормализуем ВХОДНОЙ путь для единообразия + NormalizedPath := NormalizePath(Path); + + // Получаем PATH из реестра (НЕ нормализуем — там могут быть пути с backslash) + if not RegQueryStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', EnvPath) then + exit; + + // Добавляем разделители по краям для корректного поиска подстроки + SearchIn := ';' + Uppercase(EnvPath) + ';'; + + // Ищем путь в EnvPath в двух вариантах: + // 1. Без trailing backslash (C:\MyApp) + // 2. С trailing backslash (C:\MyApp\) — на случай, если так записано в реестре + if Pos(';' + Uppercase(NormalizedPath) + ';', SearchIn) > 0 then + Result := True + else if Pos(';' + Uppercase(NormalizedPath) + '\;', SearchIn) > 0 then + Result := True; +end; - if not RegQueryStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', OrigPath) then +// Добавляет путь в переменную окружения PATH +// Возвращает True при успешном добавлении +function AddToPath(Path: string): Boolean; +var + EnvPath: string; + NewPath: string; + NormalizedPath: string; +begin + Result := False; + NormalizedPath := NormalizePath(Path); + + // Проверяем, есть ли путь уже в PATH + if PathExistsInEnv(NormalizedPath) then begin + Log('Путь уже существует в PATH: ' + NormalizedPath); Result := True; exit; end; - // Check if our path already exists in PATH (case insensitive) - // Check without trailing backslash - Result := Pos(';' + Uppercase(ParamExpanded) + ';', ';' + Uppercase(OrigPath) + ';') = 0; - // Also check with trailing backslash variant - if Result = True then - Result := Pos(';' + Uppercase(ParamExpanded) + '\' + ';', ';' + Uppercase(OrigPath) + ';') = 0; + + // Получаем текущее значение PATH + if RegQueryStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', EnvPath) then + begin + // Добавляем разделитель, если PATH не заканчивается на него + if Length(EnvPath) = 0 then + NewPath := NormalizedPath + else if EnvPath[Length(EnvPath)] = ';' then + NewPath := EnvPath + NormalizedPath + else + NewPath := EnvPath + ';' + NormalizedPath; + end + else + begin + // PATH не существует, создаём новый + NewPath := NormalizedPath; + end; + + // Записываем обновлённый PATH в реестр + if RegWriteStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', NewPath) then + begin + Log('Добавлено в PATH: ' + NormalizedPath); + Result := True; + end + else + Log('Ошибка при добавлении в PATH: ' + NormalizedPath); end; -procedure CurStepChanged(CurStep: TSetupStep); +// Удаляет путь из переменной окружения PATH +// Использует TStringList для парсинга и ручную сборку строки для записи, +// чтобы избежать проблемы с кавычками вокруг путей с пробелами +function RemoveFromPath(Path: string): Boolean; var - OrigPath: string; + EnvPath: string; NewPath: string; - AppPath: string; + NormalizedPath: string; + PathList: TStringList; + PathItem: string; + NormalizedItem: string; + i: Integer; + Removed: Boolean; begin - if CurStep = ssPostInstall then + Result := False; + Removed := False; + NormalizedPath := NormalizePath(Path); + + // Получаем текущее значение PATH + if not RegQueryStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', EnvPath) then begin - AppPath := ExpandConstant('{app}'); - if NeedsAddPath(AppPath) then + Log('PATH не найден в реестре'); + exit; + end; + + PathList := TStringList.Create; + try + // Разбираем PATH на элементы + PathList.Delimiter := ';'; + PathList.StrictDelimiter := True; + PathList.DelimitedText := EnvPath; + + // Удаляем наш путь и пустые элементы + for i := PathList.Count - 1 downto 0 do begin - if RegQueryStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', OrigPath) then + PathItem := PathList[i]; + + // Удаляем пустые элементы (защита от ";;" в PATH) + if Trim(PathItem) = '' then begin - // Add semicolon separator if PATH doesn't already end with one - if (Length(OrigPath) > 0) and (OrigPath[Length(OrigPath)] = ';') then - NewPath := OrigPath + AppPath - else - NewPath := OrigPath + ';' + AppPath; - - if RegWriteStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', NewPath) then - Log('Added to PATH: ' + AppPath) - else - Log('Failed to add to PATH'); - end - else + PathList.Delete(i); + Continue; + end; + + // Нормализуем для сравнения (убираем завершающий слеш) + NormalizedItem := NormalizePath(PathItem); + + // Сравниваем без учёта регистра + if Uppercase(NormalizedItem) = Uppercase(NormalizedPath) then begin - // PATH doesn't exist, create it - if RegWriteStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', AppPath) then - Log('Created PATH with: ' + AppPath) - else - Log('Failed to create PATH'); + Log('Удалено из PATH: ' + PathItem); + PathList.Delete(i); + Removed := True; end; + end; + + // Собираем строку вручную, чтобы избежать добавления кавычек + // (TStringList.DelimitedText добавляет кавычки к путям с пробелами) + NewPath := ''; + for i := 0 to PathList.Count - 1 do + begin + if i > 0 then + NewPath := NewPath + ';'; + NewPath := NewPath + PathList[i]; + end; + + // Записываем обновлённый PATH в реестр + if RegWriteStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', NewPath) then + begin + if Removed then + Log('PATH успешно обновлён') + else + Log('Путь не найден в PATH: ' + NormalizedPath); + Result := True; end else - Log('Path already in PATH, skipping'); + Log('Ошибка при обновлении PATH'); + finally + PathList.Free; + end; +end; + +procedure CurStepChanged(CurStep: TSetupStep); +var + AppPath: string; +begin + if CurStep = ssPostInstall then + begin + AppPath := ExpandConstant('{app}'); + AddToPath(AppPath); end; end; procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); var - OrigPath: string; - NewPath: string; AppPath: string; - PathList: TStringList; - PathItem: string; - i: Integer; begin if CurUninstallStep = usPostUninstall then begin AppPath := ExpandConstant('{app}'); - // Normalize: remove trailing backslash - if (Length(AppPath) > 0) and (AppPath[Length(AppPath)] = '\') then - AppPath := Copy(AppPath, 1, Length(AppPath) - 1); - - if RegQueryStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', OrigPath) then - begin - PathList := TStringList.Create; - try - PathList.Delimiter := ';'; - PathList.StrictDelimiter := True; - PathList.DelimitedText := OrigPath; - - // Remove our path from the list (both with and without trailing backslash) - // Also remove empty entries to avoid ";;" in PATH - for i := PathList.Count - 1 downto 0 do - begin - PathItem := PathList[i]; - - // Remove empty entries - if Trim(PathItem) = '' then - begin - PathList.Delete(i); - Continue; - end; - - // Normalize by removing trailing backslash for comparison - if (Length(PathItem) > 0) and (PathItem[Length(PathItem)] = '\') then - PathItem := Copy(PathItem, 1, Length(PathItem) - 1); - - if Uppercase(PathItem) = Uppercase(AppPath) then - begin - Log('Removed from PATH: ' + PathList[i]); - PathList.Delete(i); - end; - end; - - NewPath := PathList.DelimitedText; - RegWriteStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', NewPath); - finally - PathList.Free; - end; - end; + RemoveFromPath(AppPath); end; end;