Skip to content

Commit d9ea947

Browse files
authored
Improve NSIS/MSI Installers for upgrades (#68962)
* fix(nsis): repair MSI uninstall flow and handle msiexec exit codes Rename the post-prompt label so IDOK no longer collides with Function UninstallMSI (which could jump back to the MessageBox or fall through to Abort). Route Cancel explicitly to Abort. After msiexec, log the exit code; treat 3010 and 1641 as reboot-pending success. On other non-zero codes, show an error dialog only in non-silent mode; silent installs log and abort without a blocking MessageBox. * Use 32bit NSIS * Use the NSIS Uninstaller to remove existing NSIS Install * MSI/NSIS: strip __pycache__ and stray .pyc under install dir on upgrade and uninstall Add cutil.clear_python_bytecode_caches_under_dir: walk INSTALLDIR, remove __pycache__ trees (deepest first), then delete orphaned *.pyc. New immediate CA clear_python_caches_IMCAC runs after kill_python_exe before InstallValidate when not doing a full uninstall (NOT REMOVE=ALL). DeleteConfig_DECAC now invokes the same sweep first so uninstall and CLEAN_INSTALL/DeleteConfig2 paths clear bytecode MSI never tracked. After UninstallMSI in Salt-Minion-Setup.nsi, capture install_dir from the registry (with Program Files fallback), then run a cmd FOR/RD sweep for __pycache__ and *.pyc so MSI-to-NSIS upgrades do not keep old caches from pre-fix MSIs. Document the behavior in Product-README.md. * Use nsExec instead of ExecWait * Fix pre-commit
1 parent c8e5709 commit d9ea947

7 files changed

Lines changed: 755 additions & 211 deletions

File tree

pkg/windows/install_salt.ps1

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,13 @@ if ( ! $SkipInstall ) {
249249
if ( $SourceTarball ) {
250250
$InstallPath = $SourceTarball
251251
} else {
252+
# If we don't have a $SourceTarball, we're building outside of CICD
253+
# The man files are generated by CICD and required by setup.py
254+
# So, we need to create dummy files in order for build to succeed
252255
$InstallPath = "."
256+
New-Item -Path .\doc\man\salt-call.1 -Type File -Force | Out-Null
257+
New-Item -Path .\doc\man\salt-cp.1 -Type File -Force | Out-Null
258+
New-Item -Path .\doc\man\salt-minion.1 -Type File -Force | Out-Null
253259
}
254260
try {
255261
$env:RELENV_PIP_DIR = "yes"

pkg/windows/msi/CustomAction01/CustomAction01.cs

Lines changed: 263 additions & 89 deletions
Large diffs are not rendered by default.

pkg/windows/msi/CustomAction01/CustomAction01Util.cs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Microsoft.Tools.WindowsInstallerXml;
33
using Microsoft.Win32;
44
using System;
5+
using System.Collections.Generic;
56
using System.Diagnostics;
67
using System.IO;
78
using System.Management; // Reference C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Management.dll
@@ -38,6 +39,135 @@ public static void del_dir(Session session, string a_dir, string sub_dir = "") {
3839
}
3940
}
4041

42+
/// <summary>
43+
/// Remove runtime Python bytecode under a managed install root: all
44+
/// __pycache__ directories (deepest first), then stray .pyc files, then prune
45+
/// directories that became empty. Safe if the tree is missing or partial.
46+
/// Used from MSI clear_python_caches_IMCAC and from DeleteConfig_DECAC (uninstall
47+
/// and DeleteConfig2 / CLEAN_INSTALL).
48+
/// </summary>
49+
public static void clear_python_bytecode_caches_under_dir(Session session, string installRoot) {
50+
if (installRoot == null) installRoot = "";
51+
installRoot = installRoot.Trim().TrimEnd('\\', '/');
52+
if (installRoot.Length == 0) {
53+
session.Log("...clear_python_bytecode_caches_under_dir: skip (empty path)");
54+
return;
55+
}
56+
if (!Directory.Exists(installRoot)) {
57+
session.Log("...clear_python_bytecode_caches_under_dir: skip (not found): " + installRoot);
58+
return;
59+
}
60+
session.Log("...clear_python_bytecode_caches_under_dir: " + installRoot);
61+
List<string> pycaches = new List<string>();
62+
try {
63+
Stack<string> stack = new Stack<string>();
64+
stack.Push(installRoot);
65+
while (stack.Count > 0) {
66+
string dir = stack.Pop();
67+
string[] subdirs;
68+
try {
69+
subdirs = Directory.GetDirectories(dir);
70+
} catch (Exception ex) {
71+
cutil.just_ExceptionLog("clear_pyc GetDirectories", session, ex);
72+
continue;
73+
}
74+
foreach (string sub in subdirs) {
75+
string leaf = Path.GetFileName(sub);
76+
if (string.Compare(leaf, "__pycache__", StringComparison.OrdinalIgnoreCase) == 0)
77+
pycaches.Add(sub);
78+
else
79+
stack.Push(sub);
80+
}
81+
}
82+
} catch (Exception ex) {
83+
cutil.just_ExceptionLog("clear_pyc walk __pycache__", session, ex);
84+
}
85+
pycaches.Sort(delegate(string a, string b) { return b.Length.CompareTo(a.Length); });
86+
foreach (string p in pycaches) {
87+
try {
88+
session.Log("...clear_python_bytecode_caches_under_dir: rmdir " + p);
89+
Directory.Delete(p, true);
90+
} catch (Exception ex) {
91+
cutil.just_ExceptionLog("clear_pyc rmdir", session, ex);
92+
}
93+
}
94+
try {
95+
Stack<string> stack = new Stack<string>();
96+
stack.Push(installRoot);
97+
while (stack.Count > 0) {
98+
string dir = stack.Pop();
99+
string[] files;
100+
try {
101+
files = Directory.GetFiles(dir, "*.pyc");
102+
} catch (Exception ex) {
103+
cutil.just_ExceptionLog("clear_pyc GetFiles", session, ex);
104+
files = new string[0];
105+
}
106+
foreach (string f in files) {
107+
try {
108+
File.Delete(f);
109+
session.Log("...clear_python_bytecode_caches_under_dir: del " + f);
110+
} catch (Exception ex) {
111+
cutil.just_ExceptionLog("clear_pyc del pyc", session, ex);
112+
}
113+
}
114+
try {
115+
foreach (string sub in Directory.GetDirectories(dir))
116+
stack.Push(sub);
117+
} catch (Exception ex) {
118+
cutil.just_ExceptionLog("clear_pyc subdirs", session, ex);
119+
}
120+
}
121+
} catch (Exception ex) {
122+
cutil.just_ExceptionLog("clear_pyc stray .pyc", session, ex);
123+
}
124+
remove_empty_directories_under(session, installRoot);
125+
}
126+
127+
/// <summary>
128+
/// Delete leaf empty directories under installRoot (deepest first).
129+
/// Does not remove installRoot itself.
130+
/// </summary>
131+
private static void remove_empty_directories_under(Session session, string installRoot) {
132+
if (installRoot == null || installRoot.Length == 0 || !Directory.Exists(installRoot))
133+
return;
134+
string root;
135+
try {
136+
root = Path.GetFullPath(installRoot);
137+
} catch (Exception ex) {
138+
cutil.just_ExceptionLog("clear_pyc emptydirs root", session, ex);
139+
return;
140+
}
141+
List<string> dirs = new List<string>();
142+
try {
143+
foreach (string d in Directory.GetDirectories(root, "*", SearchOption.AllDirectories))
144+
dirs.Add(d);
145+
} catch (Exception ex) {
146+
cutil.just_ExceptionLog("clear_pyc emptydirs enumerate", session, ex);
147+
return;
148+
}
149+
dirs.Sort(delegate(string a, string b) { return b.Length.CompareTo(a.Length); });
150+
foreach (string d in dirs) {
151+
if (!Directory.Exists(d))
152+
continue;
153+
try {
154+
if (string.Compare(Path.GetFullPath(d), root, StringComparison.OrdinalIgnoreCase) == 0)
155+
continue;
156+
} catch (Exception ex) {
157+
cutil.just_ExceptionLog("clear_pyc emptydirs norm", session, ex);
158+
continue;
159+
}
160+
try {
161+
if (Directory.GetFileSystemEntries(d).Length > 0)
162+
continue;
163+
session.Log("...clear_python_bytecode_caches_under_dir: rmdir empty " + d);
164+
Directory.Delete(d, false);
165+
} catch (Exception ex) {
166+
cutil.just_ExceptionLog("clear_pyc rmdir empty", session, ex);
167+
}
168+
}
169+
}
170+
41171

42172
public static void del_registry_key(Session session, String HKLM_reg_path) {
43173
try {

0 commit comments

Comments
 (0)