win-nice 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,192 @@
1
+ # SPDX-License-Identifier: MIT OR Apache-2.0
2
+ # win-nice: managed-file
3
+ # No param(): nothing here needs a named parameter, and $args sidesteps
4
+ # PowerShell's parameter binder entirely - see cap.ps1 for why that matters.
5
+ $Command = $args
6
+
7
+ if (-not $Command -or $Command.Count -eq 0) {
8
+ Write-Error "usage: belownormal <command> [args...]"
9
+ exit 1
10
+ }
11
+
12
+ # Fallback command line for when the target isn't a directly-launchable .exe (see
13
+ # BelowNormalLauncher.Run below) - re-parsed by cmd.exe (via "cmd.exe /c"), so quoting must
14
+ # neutralize its operators (&|<>^) and not just whitespace - see cap.ps1 for the
15
+ # same logic and its documented "%" limitation. belownormal.bat has its own, more
16
+ # severe "%" caveat (see there) that applies before this script ever runs.
17
+ $commandLine = ($Command | ForEach-Object {
18
+ $escaped = $_ -replace '"', '\"'
19
+ if ($escaped -eq '' -or $escaped -match '[\s"&|<>^]') { '"' + $escaped + '"' } else { $escaped }
20
+ }) -join ' '
21
+
22
+ $source = @"
23
+ using System;
24
+ using System.Runtime.InteropServices;
25
+ using System.Text;
26
+
27
+ public static class BelowNormalLauncher
28
+ {
29
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
30
+ struct STARTUPINFO
31
+ {
32
+ public int cb;
33
+ public string lpReserved;
34
+ public string lpDesktop;
35
+ public string lpTitle;
36
+ public int dwX;
37
+ public int dwY;
38
+ public int dwXSize;
39
+ public int dwYSize;
40
+ public int dwXCountChars;
41
+ public int dwYCountChars;
42
+ public int dwFillAttribute;
43
+ public int dwFlags;
44
+ public short wShowWindow;
45
+ public short cbReserved2;
46
+ public IntPtr lpReserved2;
47
+ public IntPtr hStdInput;
48
+ public IntPtr hStdOutput;
49
+ public IntPtr hStdError;
50
+ }
51
+
52
+ [StructLayout(LayoutKind.Sequential)]
53
+ struct PROCESS_INFORMATION
54
+ {
55
+ public IntPtr hProcess;
56
+ public IntPtr hThread;
57
+ public int dwProcessId;
58
+ public int dwThreadId;
59
+ }
60
+
61
+ [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
62
+ static extern bool CreateProcess(string lpApplicationName, StringBuilder lpCommandLine,
63
+ IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles,
64
+ uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory,
65
+ ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
66
+
67
+ [DllImport("kernel32.dll", SetLastError = true)]
68
+ static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
69
+
70
+ [DllImport("kernel32.dll", SetLastError = true)]
71
+ static extern bool GetExitCodeProcess(IntPtr hProcess, out uint lpExitCode);
72
+
73
+ [DllImport("kernel32.dll")]
74
+ static extern bool CloseHandle(IntPtr hObject);
75
+
76
+ // Standard MSVCRT/CommandLineToArgvW quoting: safe for a directly-launched .exe's
77
+ // own argv parsing. No cmd.exe involved on this path, so none of its operator or
78
+ // "%" expansion semantics apply - this is the safe path, used whenever possible.
79
+ static string ArgvQuote(string arg)
80
+ {
81
+ if (arg.Length > 0 && arg.IndexOfAny(new char[] { ' ', '\t', '\n', '\v', '"' }) < 0)
82
+ return arg;
83
+
84
+ var result = new StringBuilder();
85
+ result.Append('"');
86
+ int backslashes = 0;
87
+ foreach (char c in arg)
88
+ {
89
+ if (c == '\\')
90
+ {
91
+ backslashes++;
92
+ }
93
+ else if (c == '"')
94
+ {
95
+ result.Append('\\', backslashes * 2 + 1);
96
+ result.Append('"');
97
+ backslashes = 0;
98
+ }
99
+ else
100
+ {
101
+ if (backslashes > 0) { result.Append('\\', backslashes); backslashes = 0; }
102
+ result.Append(c);
103
+ }
104
+ }
105
+ if (backslashes > 0) result.Append('\\', backslashes * 2);
106
+ result.Append('"');
107
+ return result.ToString();
108
+ }
109
+
110
+ static string BuildArgvCommandLine(string[] argv)
111
+ {
112
+ var parts = new string[argv.Length];
113
+ for (int i = 0; i < argv.Length; i++) parts[i] = ArgvQuote(argv[i]);
114
+ return string.Join(" ", parts);
115
+ }
116
+
117
+ // Priority is passed via dwCreationFlags, applied atomically at creation - no
118
+ // Job Object needed. Windows' CreateProcess inherits IDLE/BELOW_NORMAL priority
119
+ // by default to children that don't request a priority of their own.
120
+ public static int Run(uint priorityClass, string[] argv, string cmdExeCommandLine)
121
+ {
122
+ var si = new STARTUPINFO();
123
+ si.cb = Marshal.SizeOf(si);
124
+ PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
125
+
126
+ // See cap.ps1 for why .bat/.cmd targets skip the direct attempt entirely:
127
+ // CreateProcess silently re-invokes them through cmd.exe on its own, using
128
+ // unescaped text, instead of failing the way a genuinely missing exe would.
129
+ bool isBatOrCmd = argv.Length > 0 && (
130
+ argv[0].EndsWith(".bat", StringComparison.OrdinalIgnoreCase) ||
131
+ argv[0].EndsWith(".cmd", StringComparison.OrdinalIgnoreCase));
132
+
133
+ bool created = false;
134
+ if (!isBatOrCmd)
135
+ {
136
+ var directCommandLine = new StringBuilder(BuildArgvCommandLine(argv));
137
+ created = CreateProcess(null, directCommandLine, IntPtr.Zero, IntPtr.Zero, true,
138
+ priorityClass, IntPtr.Zero, null, ref si, out pi);
139
+ }
140
+
141
+ if (!created)
142
+ {
143
+ // Falling back to cmd.exe /c: a literal "%" in any argument could now
144
+ // trigger environment-variable expansion (cmd.exe pairs up "%" characters
145
+ // across the whole command line, even across separate arguments) and
146
+ // change what actually runs. Fail loudly here instead of silently risking
147
+ // that - there's no reliable per-character escape for "%" at this level.
148
+ foreach (var a in argv)
149
+ {
150
+ if (a.IndexOf('%') >= 0)
151
+ throw new InvalidOperationException(
152
+ "Refusing to run: argument contains '%' and the target needs the cmd.exe " +
153
+ "fallback (not a directly-launchable .exe), where '%' can trigger unintended " +
154
+ "environment-variable expansion. See README's Argument handling section.");
155
+ }
156
+
157
+ string cmdExe = Environment.SystemDirectory + "\\cmd.exe";
158
+ // /d: skip HKCU AutoRun (user-writable registry key). /v:off: disable delayed
159
+ // expansion so "!var!" in an argument can't be expanded. /s plus the extra outer
160
+ // quote pair: cmd's /S rule strips exactly that outer pair and leaves the rest of
161
+ // the string untouched - without /S, cmd strips the first and last quote of the
162
+ // whole line instead, which breaks quoting whenever the target path itself needs
163
+ // quotes AND another argument is also quoted.
164
+ var shellCommandLine = new StringBuilder("\"" + cmdExe + "\" /d /v:off /s /c \"" + cmdExeCommandLine + "\"");
165
+ created = CreateProcess(null, shellCommandLine, IntPtr.Zero, IntPtr.Zero, true,
166
+ priorityClass, IntPtr.Zero, null, ref si, out pi);
167
+ if (!created)
168
+ throw new InvalidOperationException("CreateProcess failed: " + Marshal.GetLastWin32Error());
169
+ }
170
+
171
+ WaitForSingleObject(pi.hProcess, 0xFFFFFFFF);
172
+
173
+ uint exitCode;
174
+ GetExitCodeProcess(pi.hProcess, out exitCode);
175
+
176
+ CloseHandle(pi.hThread);
177
+ CloseHandle(pi.hProcess);
178
+
179
+ return (int)exitCode;
180
+ }
181
+ }
182
+ "@
183
+
184
+ Add-Type -TypeDefinition $source -Language CSharp
185
+
186
+ $BELOW_NORMAL_PRIORITY_CLASS = 0x00004000
187
+ try {
188
+ exit ([BelowNormalLauncher]::Run($BELOW_NORMAL_PRIORITY_CLASS, [string[]]$Command, $commandLine))
189
+ } catch {
190
+ Write-Error $_.Exception.InnerException.Message
191
+ exit 1
192
+ }
package/bin/cap ADDED
@@ -0,0 +1,11 @@
1
+ #!/bin/sh
2
+ # SPDX-License-Identifier: MIT OR Apache-2.0
3
+ # win-nice: managed-file
4
+ # Git Bash ignores PATHEXT for bare-name resolution; this shim covers that shell.
5
+ # MSYS2_ARG_CONV_EXCL='*' stops Git Bash/MSYS from rewriting user arguments
6
+ # (e.g. /c, /d, C:\...) into Windows paths before the exec; the shim's own
7
+ # .ps1 path is converted explicitly with cygpath -w so -File gets a Windows path.
8
+ MSYS2_ARG_CONV_EXCL='*'
9
+ export MSYS2_ARG_CONV_EXCL
10
+ script=$(cygpath -w "$(dirname "$0")/cap.ps1" 2>/dev/null) || script="$(dirname "$0")/cap.ps1"
11
+ exec powershell -NoProfile -ExecutionPolicy Bypass -File "$script" "$@"
package/bin/cap.bat ADDED
@@ -0,0 +1,10 @@
1
+ @echo off
2
+ :: SPDX-License-Identifier: MIT OR Apache-2.0
3
+ :: win-nice: managed-file
4
+ :: A literal "%" in any argument gets corrupted here (cmd.exe rescans %1/%* for
5
+ :: %...% patterns the moment a batch file reads them - confirmed with nothing more
6
+ :: than a bare "echo %1", no forwarding involved; there's no per-character escape
7
+ :: for this from inside a .bat). Every other cmd.exe metacharacter (&|<>^) survives
8
+ :: this hop untouched. Invoking "cap" bare from an actual PowerShell session skips
9
+ :: this file entirely (PowerShell prefers cap.ps1) and has no "%" problem at all.
10
+ powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0cap.ps1" %*
package/bin/cap.ps1 ADDED
@@ -0,0 +1,269 @@
1
+ # SPDX-License-Identifier: MIT OR Apache-2.0
2
+ # win-nice: managed-file
3
+ # Deliberately no param()/[CmdletBinding()]: a declared parameter name (even
4
+ # without a [Parameter()] attribute) can still be ambiguously prefix-matched by
5
+ # flags meant for the wrapped command (e.g. "-p" matching "-Percent"). Reading
6
+ # everything from $args sidesteps PowerShell's parameter binder entirely.
7
+ if ($args.Count -lt 2) {
8
+ Write-Error "usage: cap <percent 1-100> <command> [args...]"
9
+ exit 1
10
+ }
11
+ $percentValue = 0
12
+ if (-not [int]::TryParse($args[0], [ref]$percentValue) -or $percentValue -lt 1 -or $percentValue -gt 100) {
13
+ Write-Error "usage: cap <percent 1-100> <command> [args...]"
14
+ exit 1
15
+ }
16
+ $Command = @($args[1..($args.Count - 1)])
17
+
18
+ # Fallback command line for when the target isn't a directly-launchable .exe (see
19
+ # CapLauncher.Run below) - re-parsed by cmd.exe (via "cmd.exe /c"), so quoting must
20
+ # neutralize its operators (&|<>^) and not just whitespace, or e.g. "A&B" gets split
21
+ # into two commands. NOTE: a literal "%" in an argument can still trigger cmd.exe
22
+ # environment-variable expansion (e.g. "%PATH%") even when quoted, and cmd.exe pairs
23
+ # up "%" characters across argument/quote boundaries - two unrelated arguments that
24
+ # each contain one "%" can corrupt each other. There is no reliable per-character
25
+ # escape for this at the cmd.exe /c level; it's a known, inherent limitation shared
26
+ # by anything that shells out through cmd.exe (Node's own child_process included).
27
+ # This fallback path only runs for .bat/.cmd/builtin targets - a direct .exe target
28
+ # never goes through cmd.exe at all, so it isn't exposed to this limitation.
29
+ $commandLine = ($Command | ForEach-Object {
30
+ $escaped = $_ -replace '"', '\"'
31
+ if ($escaped -eq '' -or $escaped -match '[\s"&|<>^]') { '"' + $escaped + '"' } else { $escaped }
32
+ }) -join ' '
33
+
34
+ $source = @"
35
+ using System;
36
+ using System.Runtime.InteropServices;
37
+ using System.Text;
38
+
39
+ public static class CapLauncher
40
+ {
41
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
42
+ struct STARTUPINFO
43
+ {
44
+ public int cb;
45
+ public string lpReserved;
46
+ public string lpDesktop;
47
+ public string lpTitle;
48
+ public int dwX;
49
+ public int dwY;
50
+ public int dwXSize;
51
+ public int dwYSize;
52
+ public int dwXCountChars;
53
+ public int dwYCountChars;
54
+ public int dwFillAttribute;
55
+ public int dwFlags;
56
+ public short wShowWindow;
57
+ public short cbReserved2;
58
+ public IntPtr lpReserved2;
59
+ public IntPtr hStdInput;
60
+ public IntPtr hStdOutput;
61
+ public IntPtr hStdError;
62
+ }
63
+
64
+ [StructLayout(LayoutKind.Sequential)]
65
+ struct PROCESS_INFORMATION
66
+ {
67
+ public IntPtr hProcess;
68
+ public IntPtr hThread;
69
+ public int dwProcessId;
70
+ public int dwThreadId;
71
+ }
72
+
73
+ [StructLayout(LayoutKind.Sequential)]
74
+ struct JOBOBJECT_CPU_RATE_CONTROL_INFORMATION
75
+ {
76
+ public uint ControlFlags;
77
+ public uint CpuRate;
78
+ }
79
+
80
+ [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
81
+ static extern bool CreateProcess(string lpApplicationName, StringBuilder lpCommandLine,
82
+ IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles,
83
+ uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory,
84
+ ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
85
+
86
+ [DllImport("kernel32.dll", SetLastError = true)]
87
+ static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string lpName);
88
+
89
+ [DllImport("kernel32.dll", SetLastError = true)]
90
+ static extern bool SetInformationJobObject(IntPtr hJob, int JobObjectInfoClass, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);
91
+
92
+ [DllImport("kernel32.dll", SetLastError = true)]
93
+ static extern bool AssignProcessToJobObject(IntPtr hJob, IntPtr hProcess);
94
+
95
+ [DllImport("kernel32.dll", SetLastError = true)]
96
+ static extern uint ResumeThread(IntPtr hThread);
97
+
98
+ [DllImport("kernel32.dll", SetLastError = true)]
99
+ static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
100
+
101
+ [DllImport("kernel32.dll", SetLastError = true)]
102
+ static extern bool GetExitCodeProcess(IntPtr hProcess, out uint lpExitCode);
103
+
104
+ [DllImport("kernel32.dll", SetLastError = true)]
105
+ static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode);
106
+
107
+ [DllImport("kernel32.dll")]
108
+ static extern bool CloseHandle(IntPtr hObject);
109
+
110
+ const uint CREATE_SUSPENDED = 0x00000004;
111
+ const int JobObjectCpuRateControlInformation = 15;
112
+ const uint JOB_OBJECT_CPU_RATE_CONTROL_ENABLE = 0x1;
113
+ const uint JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP = 0x4;
114
+
115
+ // Standard MSVCRT/CommandLineToArgvW quoting: safe for a directly-launched .exe's
116
+ // own argv parsing. No cmd.exe involved on this path, so none of its operator or
117
+ // "%" expansion semantics apply - this is the safe path, used whenever possible.
118
+ static string ArgvQuote(string arg)
119
+ {
120
+ if (arg.Length > 0 && arg.IndexOfAny(new char[] { ' ', '\t', '\n', '\v', '"' }) < 0)
121
+ return arg;
122
+
123
+ var result = new StringBuilder();
124
+ result.Append('"');
125
+ int backslashes = 0;
126
+ foreach (char c in arg)
127
+ {
128
+ if (c == '\\')
129
+ {
130
+ backslashes++;
131
+ }
132
+ else if (c == '"')
133
+ {
134
+ result.Append('\\', backslashes * 2 + 1);
135
+ result.Append('"');
136
+ backslashes = 0;
137
+ }
138
+ else
139
+ {
140
+ if (backslashes > 0) { result.Append('\\', backslashes); backslashes = 0; }
141
+ result.Append(c);
142
+ }
143
+ }
144
+ if (backslashes > 0) result.Append('\\', backslashes * 2);
145
+ result.Append('"');
146
+ return result.ToString();
147
+ }
148
+
149
+ static string BuildArgvCommandLine(string[] argv)
150
+ {
151
+ var parts = new string[argv.Length];
152
+ for (int i = 0; i < argv.Length; i++) parts[i] = ArgvQuote(argv[i]);
153
+ return string.Join(" ", parts);
154
+ }
155
+
156
+ public static int Run(int percent, string[] argv, string cmdExeCommandLine)
157
+ {
158
+ IntPtr hJob = CreateJobObject(IntPtr.Zero, null);
159
+ if (hJob == IntPtr.Zero)
160
+ throw new InvalidOperationException("CreateJobObject failed: " + Marshal.GetLastWin32Error());
161
+
162
+ var cpuInfo = new JOBOBJECT_CPU_RATE_CONTROL_INFORMATION
163
+ {
164
+ ControlFlags = JOB_OBJECT_CPU_RATE_CONTROL_ENABLE | JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP,
165
+ CpuRate = (uint)(percent * 100)
166
+ };
167
+ int size = Marshal.SizeOf(cpuInfo);
168
+ IntPtr ptr = Marshal.AllocHGlobal(size);
169
+ Marshal.StructureToPtr(cpuInfo, ptr, false);
170
+ bool ok = SetInformationJobObject(hJob, JobObjectCpuRateControlInformation, ptr, (uint)size);
171
+ Marshal.FreeHGlobal(ptr);
172
+ if (!ok)
173
+ {
174
+ CloseHandle(hJob);
175
+ throw new InvalidOperationException("SetInformationJobObject failed: " + Marshal.GetLastWin32Error());
176
+ }
177
+
178
+ var si = new STARTUPINFO();
179
+ si.cb = Marshal.SizeOf(si);
180
+ PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
181
+
182
+ // Try launching the target directly first (no shell at all) - unless it's a
183
+ // .bat/.cmd file. CreateProcess has an undocumented-but-real fallback of its
184
+ // own for those: instead of failing, it silently re-invokes them through
185
+ // cmd.exe using OUR unescaped argv text (ArgvQuote only protects CRT argv
186
+ // parsing, not cmd.exe's operators), reopening the exact "A&B" splits this
187
+ // whole file exists to prevent. A bare name with no extension is safe either
188
+ // way: CreateProcess only ever auto-appends ".exe" to it, never ".bat/.cmd",
189
+ // so it fails cleanly (ERROR_FILE_NOT_FOUND) when only a same-named .bat/.cmd
190
+ // exists, and falls through to the escaped path below.
191
+ bool isBatOrCmd = argv.Length > 0 && (
192
+ argv[0].EndsWith(".bat", StringComparison.OrdinalIgnoreCase) ||
193
+ argv[0].EndsWith(".cmd", StringComparison.OrdinalIgnoreCase));
194
+
195
+ bool created = false;
196
+ if (!isBatOrCmd)
197
+ {
198
+ var directCommandLine = new StringBuilder(BuildArgvCommandLine(argv));
199
+ created = CreateProcess(null, directCommandLine, IntPtr.Zero, IntPtr.Zero, true,
200
+ CREATE_SUSPENDED, IntPtr.Zero, null, ref si, out pi);
201
+ }
202
+
203
+ if (!created)
204
+ {
205
+ // Falling back to cmd.exe /c: a literal "%" in any argument could now
206
+ // trigger environment-variable expansion (cmd.exe pairs up "%" characters
207
+ // across the whole command line, even across separate arguments) and
208
+ // change what actually runs. Fail loudly here instead of silently risking
209
+ // that - there's no reliable per-character escape for "%" at this level.
210
+ foreach (var a in argv)
211
+ {
212
+ if (a.IndexOf('%') >= 0)
213
+ throw new InvalidOperationException(
214
+ "Refusing to run: argument contains '%' and the target needs the cmd.exe " +
215
+ "fallback (not a directly-launchable .exe), where '%' can trigger unintended " +
216
+ "environment-variable expansion. See README's Argument handling section.");
217
+ }
218
+
219
+ string cmdExe = Environment.SystemDirectory + "\\cmd.exe";
220
+ // /d: skip HKCU AutoRun (user-writable registry key). /v:off: disable delayed
221
+ // expansion so "!var!" in an argument can't be expanded. /s plus the extra outer
222
+ // quote pair: cmd's /S rule strips exactly that outer pair and leaves the rest of
223
+ // the string untouched - without /S, cmd strips the first and last quote of the
224
+ // whole line instead, which breaks quoting whenever the target path itself needs
225
+ // quotes AND another argument is also quoted.
226
+ var shellCommandLine = new StringBuilder("\"" + cmdExe + "\" /d /v:off /s /c \"" + cmdExeCommandLine + "\"");
227
+ created = CreateProcess(null, shellCommandLine, IntPtr.Zero, IntPtr.Zero, true,
228
+ CREATE_SUSPENDED, IntPtr.Zero, null, ref si, out pi);
229
+ if (!created)
230
+ {
231
+ CloseHandle(hJob);
232
+ throw new InvalidOperationException("CreateProcess failed: " + Marshal.GetLastWin32Error());
233
+ }
234
+ }
235
+
236
+ if (!AssignProcessToJobObject(hJob, pi.hProcess))
237
+ {
238
+ // Can't guarantee the cap - kill instead of letting it run uncapped and orphaned.
239
+ int err = Marshal.GetLastWin32Error();
240
+ TerminateProcess(pi.hProcess, 1);
241
+ CloseHandle(pi.hThread);
242
+ CloseHandle(pi.hProcess);
243
+ CloseHandle(hJob);
244
+ throw new InvalidOperationException("AssignProcessToJobObject failed: " + err);
245
+ }
246
+
247
+ ResumeThread(pi.hThread);
248
+ WaitForSingleObject(pi.hProcess, 0xFFFFFFFF);
249
+
250
+ uint exitCode;
251
+ GetExitCodeProcess(pi.hProcess, out exitCode);
252
+
253
+ CloseHandle(pi.hThread);
254
+ CloseHandle(pi.hProcess);
255
+ CloseHandle(hJob);
256
+
257
+ return (int)exitCode;
258
+ }
259
+ }
260
+ "@
261
+
262
+ Add-Type -TypeDefinition $source -Language CSharp
263
+
264
+ try {
265
+ exit ([CapLauncher]::Run($percentValue, [string[]]$Command, $commandLine))
266
+ } catch {
267
+ Write-Error $_.Exception.InnerException.Message
268
+ exit 1
269
+ }
package/bin/cx ADDED
@@ -0,0 +1,11 @@
1
+ #!/bin/sh
2
+ # SPDX-License-Identifier: MIT OR Apache-2.0
3
+ # win-nice: managed-file
4
+ # Git Bash ignores PATHEXT for bare-name resolution; this shim covers that shell.
5
+ # MSYS2_ARG_CONV_EXCL='*' stops Git Bash/MSYS from rewriting user arguments
6
+ # (e.g. /c, /d, C:\...) into Windows paths before the exec; the shim's own
7
+ # .ps1 path is converted explicitly with cygpath -w so -File gets a Windows path.
8
+ MSYS2_ARG_CONV_EXCL='*'
9
+ export MSYS2_ARG_CONV_EXCL
10
+ script=$(cygpath -w "$(dirname "$0")/cx.ps1" 2>/dev/null) || script="$(dirname "$0")/cx.ps1"
11
+ exec powershell -NoProfile -ExecutionPolicy Bypass -File "$script" "$@"
package/bin/cx.bat ADDED
@@ -0,0 +1,8 @@
1
+ @echo off
2
+ :: SPDX-License-Identifier: MIT OR Apache-2.0
3
+ :: win-nice: managed-file
4
+ :: A literal "%" in any argument gets corrupted here - see cap.bat for why (a
5
+ :: cmd.exe batch-parameter quirk, not fixable from inside a .bat). Every other
6
+ :: cmd.exe metacharacter (&|<>^) survives this hop untouched. Invoking "cx" bare
7
+ :: from an actual PowerShell session skips this file (cx.ps1 preferred).
8
+ codex --dangerously-bypass-approvals-and-sandbox %*