Skip to content

Commit dcf3ea4

Browse files
committed
Optimize how RunOnce commands are executed
1 parent 22a3e97 commit dcf3ea4

12 files changed

+115
-82
lines changed

Main.cs

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -159,26 +159,6 @@ public static string RegistryCommand(string value)
159159
return $"reg.exe {value}";
160160
}
161161

162-
public static string UserRunOnceCommand(string rootKey, string subKey, string name, string command)
163-
{
164-
return UserRunCommand(rootKey, subKey, "RunOnce", name, command);
165-
}
166-
167-
public static string RunAtLogonCommand(string rootKey, string subKey, string name, string command)
168-
{
169-
return UserRunCommand(rootKey, subKey, "Run", name, command);
170-
}
171-
172-
private static string UserRunCommand(string rootKey, string subKey, string runKey, string name, string command)
173-
{
174-
static string Escape(string s)
175-
{
176-
return s.Replace(@"""", @"\""");
177-
}
178-
179-
return RegistryCommand(@$"add ""{rootKey}\{subKey}\Software\Microsoft\Windows\CurrentVersion\{runKey}"" /v ""{Escape(name)}"" /t REG_SZ /d ""{Escape(command)}"" /f");
180-
}
181-
182162
public delegate IEnumerable<string> RegistryDefaultUserAction(string rootKey, string subKey);
183163

184164
public static IEnumerable<string> RegistryDefaultUserCommand(RegistryDefaultUserAction action)
@@ -315,6 +295,7 @@ public record class Configuration(
315295
HideModes HideFiles,
316296
bool HideEdgeFre,
317297
bool MakeEdgeUninstallable,
298+
bool LaunchToThisPC,
318299
TaskbarSearchMode TaskbarSearch,
319300
IStartPinsSettings StartPinsSettings,
320301
IStartTilesSettings StartTilesSettings,
@@ -370,13 +351,56 @@ CompactOsModes CompactOsMode
370351
HideFiles: HideModes.Hidden,
371352
HideEdgeFre: false,
372353
MakeEdgeUninstallable: false,
354+
LaunchToThisPC: false,
373355
TaskbarSearch: TaskbarSearchMode.Box,
374356
StartPinsSettings: new DefaultStartPinsSettings(),
375357
StartTilesSettings: new DefaultStartTilesSettings(),
376358
CompactOsMode: CompactOsModes.Default
377359
);
378360
}
379361

362+
/// <summary>
363+
/// Collects PowerShell commands that will be run whenever a user logs on for the first time.
364+
/// </summary>
365+
public class UserOnceScript
366+
{
367+
private bool needsExplorerRestart = false;
368+
private readonly List<string> commands = [];
369+
370+
public void Append(string command)
371+
{
372+
commands.Add(command);
373+
}
374+
375+
public void InvokeFile(string file)
376+
{
377+
Append($"Get-Content -LiteralPath '{file}' -Raw | Invoke-Expression;");
378+
}
379+
380+
public void RestartExplorer()
381+
{
382+
needsExplorerRestart = true;
383+
}
384+
385+
public string GetScript()
386+
{
387+
IEnumerable<string> Lines()
388+
{
389+
yield return "& {";
390+
foreach (string command in commands)
391+
{
392+
yield return command;
393+
}
394+
if (needsExplorerRestart)
395+
{
396+
yield return Util.StringFromResource("RestartExplorer.ps1");
397+
}
398+
yield return @"} *>&1 >> ""$env:TEMP\UserOnce.log"";";
399+
}
400+
return string.Join("\r\n", Lines());
401+
}
402+
}
403+
380404
public interface IKeyed
381405
{
382406
string Id { get; }
@@ -785,7 +809,8 @@ public XmlDocument GenerateXml(Configuration config)
785809
Configuration: config,
786810
Document: doc,
787811
NamespaceManager: ns,
788-
Generator: this
812+
Generator: this,
813+
UserOnceScript: new UserOnceScript()
789814
);
790815

791816
new List<Modifier> {
@@ -807,6 +832,7 @@ public XmlDocument GenerateXml(Configuration config)
807832
new TimeZoneModifier(context),
808833
new WdacModifier(context),
809834
new ScriptModifier(context),
835+
new UserOnceModifier(context),
810836
new OrderModifier(context),
811837
new ProcessorArchitectureModifier(context),
812838
new PrettyModifier(context),
@@ -853,7 +879,8 @@ public record class ModifierContext(
853879
XmlDocument Document,
854880
XmlNamespaceManager NamespaceManager,
855881
Configuration Configuration,
856-
UnattendGenerator Generator
882+
UnattendGenerator Generator,
883+
UserOnceScript UserOnceScript
857884
);
858885

859886
abstract class Modifier(ModifierContext context)
@@ -866,6 +893,8 @@ abstract class Modifier(ModifierContext context)
866893

867894
public UnattendGenerator Generator { get; } = context.Generator;
868895

896+
public UserOnceScript UserOnceScript { get; } = context.UserOnceScript;
897+
869898
public XmlElement NewSimpleElement(string name, XmlElement parent, string innerText)
870899
{
871900
return Util.NewSimpleElement(name, parent, innerText, Document, NamespaceManager);

UnattendGenerator.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<None Remove="resource\RemoveCapabilities.ps1" />
1616
<None Remove="resource\RemoveFeatures.ps1" />
1717
<None Remove="resource\RemovePackages.ps1" />
18+
<None Remove="resource\RestartExplorer.ps1" />
1819
<None Remove="resource\SetColorTheme.ps1" />
1920
<None Remove="resource\SetComputerName.ps1" />
2021
<None Remove="resource\SetStartPins.ps1" />
@@ -36,6 +37,7 @@
3637
<EmbeddedResource Include="resource\KeyboardIdentifier.json" />
3738
<EmbeddedResource Include="resource\Bloatware.json" />
3839
<EmbeddedResource Include="resource\known-writeable-folders.txt" />
40+
<EmbeddedResource Include="resource\RestartExplorer.ps1" />
3941
<EmbeddedResource Include="resource\SetComputerName.ps1" />
4042
<EmbeddedResource Include="resource\SetColorTheme.ps1" />
4143
<EmbeddedResource Include="resource\TaskbarIcons.ps1" />

modifier/Bloatware.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,11 +194,11 @@ public override void Process()
194194
);
195195
break;
196196
case CustomBloatwareStep when bw.Id == "RemoveCopilot":
197+
UserOnceScript.Append("Get-AppxPackage -Name 'Microsoft.Windows.Ai.Copilot.Provider' | Remove-AppxPackage;");
197198
appender.Append(
198199
CommandBuilder.RegistryDefaultUserCommand((rootKey, subKey) =>
199200
{
200201
return [
201-
CommandBuilder.UserRunOnceCommand(rootKey, subKey, "UninstallCopilot", CommandBuilder.PowerShellCommand("Get-AppxPackage -Name 'Microsoft.Windows.Ai.Copilot.Provider' | Remove-AppxPackage;")),
202202
CommandBuilder.RegistryCommand(@$"add ""{rootKey}\{subKey}\Software\Policies\Microsoft\Windows\WindowsCopilot"" /v TurnOffWindowsCopilot /t REG_DWORD /d 1 /f")
203203
];
204204
})

modifier/Locales.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,7 @@ public override void Process()
8181
if (settings.GeoLocation.Id != settings.LocaleAndKeyboard.Locale.GeoLocation?.Id)
8282
{
8383
CommandAppender appender = GetAppender(CommandConfig.Specialize);
84-
appender.Append(
85-
CommandBuilder.RegistryDefaultUserCommand((rootKey, subKey) =>
86-
{
87-
return [CommandBuilder.UserRunOnceCommand(rootKey, subKey, "GeoLocation", CommandBuilder.PowerShellCommand($"Set-WinHomeLocation -GeoId {settings.GeoLocation.Id};"))];
88-
}
89-
));
84+
UserOnceScript.Append($"Set-WinHomeLocation -GeoId {settings.GeoLocation.Id};");
9085
}
9186
}
9287
else if (Configuration.LanguageSettings is InteractiveLanguageSettings)

modifier/Optimizations.cs

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,8 @@ IEnumerable<string> SetExplorerOptions(string rootKey, string subKey)
112112
string ps1File = @"C:\Windows\Setup\Scripts\TaskbarIcons.ps1";
113113
string script = Util.StringFromResource("TaskbarIcons.ps1");
114114
AddTextFile(script, ps1File);
115-
appender.Append(
116-
CommandBuilder.RegistryDefaultUserCommand((rootKey, subKey) =>
117-
{
118-
return [CommandBuilder.UserRunOnceCommand(rootKey, subKey, "TaskbarIcons", CommandBuilder.InvokePowerShellScript(ps1File))];
119-
})
120-
);
115+
UserOnceScript.InvokeFile(ps1File);
116+
UserOnceScript.RestartExplorer();
121117
}
122118

123119
if (Configuration.DisableDefender)
@@ -255,14 +251,17 @@ IEnumerable<string> SetExplorerOptions(string rootKey, string subKey)
255251
appender.Append(
256252
CommandBuilder.RegistryDefaultUserCommand((rootKey, subKey) =>
257253
{
258-
string script = $"$mountKey = '{subKey}';\r\n" + Util.StringFromResource("TurnOffSystemSounds.ps1");
259-
string ps1File = @"%TEMP%\sounds.ps1";
260-
AddTextFile(script, ps1File);
254+
StringWriter writer = new();
255+
writer.WriteLine(@$"$mountKey = '{rootKey}\{subKey}';");
256+
writer.WriteLine(Util.StringFromResource("TurnOffSystemSounds.ps1"));
257+
string ps1File = @"%TEMP%\TurnOffSystemSounds.ps1";
258+
AddTextFile(writer.ToString(), ps1File);
261259
return [
262260
CommandBuilder.InvokePowerShellScript(ps1File),
263-
CommandBuilder.UserRunOnceCommand(rootKey, subKey, "NoSounds", CommandBuilder.RegistryCommand(@"add ""HKCU\AppEvents\Schemes"" /ve /t REG_SZ /d "".None"" /f")),
264261
];
265262
}));
263+
UserOnceScript.Append(@"Set-ItemProperty -LiteralPath 'Registry::HKCU\AppEvents\Schemes' -Name '(Default)' -Type 'String' -Value '.None';");
264+
266265
appender.Append([
267266
CommandBuilder.RegistryCommand(@"add ""HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI\BootAnimation"" /v DisableStartupSound /t REG_DWORD /d 1 /f"),
268267
CommandBuilder.RegistryCommand(@"add ""HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\EditionOverrides"" /v UserSetting_DisableStartupSound /t REG_DWORD /d 1 /f"),
@@ -344,12 +343,7 @@ IEnumerable<string> SetExplorerOptions(string rootKey, string subKey)
344343

345344
if (Configuration.ClassicContextMenu)
346345
{
347-
appender.Append(
348-
CommandBuilder.RegistryDefaultUserCommand((rootKey, subKey) =>
349-
{
350-
return [CommandBuilder.UserRunOnceCommand(rootKey, subKey, "ClassicContextMenu", CommandBuilder.RegistryCommand(@$"add ""HKCU\Software\Classes\CLSID\{{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}}\InprocServer32"" /ve /f"))];
351-
})
352-
);
346+
UserOnceScript.Append(@"New-Item -Path 'Registry::HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32' -ErrorAction 'SilentlyContinue';");
353347
}
354348

355349
if (Configuration.LeftTaskbar)
@@ -453,14 +447,13 @@ IEnumerable<string> SetExplorerOptions(string rootKey, string subKey)
453447
);
454448
}
455449
{
456-
appender.Append(
457-
CommandBuilder.RegistryDefaultUserCommand((rootKey, subKey) =>
458-
{
459-
return [
460-
CommandBuilder.UserRunOnceCommand(rootKey, subKey, "SearchboxTaskbarMode", CommandBuilder.RegistryCommand(@$"add HKCU\Software\Microsoft\Windows\CurrentVersion\Search /v SearchboxTaskbarMode /t REG_DWORD /d {Configuration.TaskbarSearch:D} /f")),
461-
];
462-
})
463-
);
450+
if (Configuration.LaunchToThisPC)
451+
{
452+
UserOnceScript.Append(@"Set-ItemProperty -LiteralPath 'Registry::HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced' -Name 'LaunchTo' -Type 'DWord' -Value 1;");
453+
}
454+
}
455+
{
456+
UserOnceScript.Append(@$"Set-ItemProperty -LiteralPath 'Registry::HKCU\Software\Microsoft\Windows\CurrentVersion\Search' -Name 'SearchboxTaskbarMode' -Type 'DWord' -Value {Configuration.TaskbarSearch:D};");
464457
}
465458
{
466459
void SetStartPins(string json)

modifier/Personalization.cs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,11 @@ public override void Process()
5454
{
5555
return [
5656
CommandBuilder.RegistryCommand(@$"add ""{rootKey}\{subKey}\SOFTWARE\Microsoft\Windows\DWM"" /v ColorPrevalence /t REG_DWORD /d {(settings.AccentColorOnBorders ? 1 : 0)} /f"),
57-
CommandBuilder.UserRunOnceCommand(rootKey, subKey, "SetColorTheme", CommandBuilder.InvokePowerShellScript(ps1File)),
5857
];
5958
})
6059
);
60+
UserOnceScript.InvokeFile(ps1File);
61+
UserOnceScript.RestartExplorer();
6162
}
6263
}
6364
{
@@ -69,14 +70,7 @@ public override void Process()
6970
writer.WriteLine($"$htmlColor = '{ColorTranslator.ToHtml(settings.Color)}';");
7071
writer.WriteLine(script);
7172
AddTextFile(writer.ToString(), ps1File);
72-
appender.Append(
73-
CommandBuilder.RegistryDefaultUserCommand((rootKey, subKey) =>
74-
{
75-
return [
76-
CommandBuilder.UserRunOnceCommand(rootKey, subKey, "SetWallpaper", CommandBuilder.InvokePowerShellScript(ps1File)),
77-
];
78-
})
79-
);
73+
UserOnceScript.InvokeFile(ps1File);
8074
}
8175
}
8276
}

modifier/Script.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -169,12 +169,14 @@ private void CallScript(ScriptInfo info)
169169
);
170170
break;
171171
case ScriptPhase.UserOnce:
172-
appender.Append(
173-
CommandBuilder.RegistryDefaultUserCommand((rootKey, subKey) =>
174-
{
175-
return [CommandBuilder.UserRunOnceCommand(rootKey, subKey, info.Key, command)];
176-
})
177-
);
172+
if (info.Script.Type == ScriptType.Ps1)
173+
{
174+
UserOnceScript.InvokeFile(info.ScriptPath);
175+
}
176+
else
177+
{
178+
UserOnceScript.Append(command + ";");
179+
}
178180
break;
179181
case ScriptPhase.DefaultUser:
180182
appender.Append(

modifier/UserOnce.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
namespace Schneegans.Unattend;
2+
3+
class UserOnceModifier(ModifierContext context) : Modifier(context)
4+
{
5+
public override void Process()
6+
{
7+
CommandAppender appender = GetAppender(CommandConfig.Specialize);
8+
string script = UserOnceScript.GetScript();
9+
string ps1File = @"C:\Windows\Setup\Scripts\UserOnce.ps1";
10+
AddTextFile(script, ps1File);
11+
12+
appender.Append(
13+
CommandBuilder.RegistryDefaultUserCommand((rootKey, subKey) =>
14+
{
15+
static string Escape(string s)
16+
{
17+
return s.Replace(@"""", @"\""");
18+
}
19+
20+
string command = CommandBuilder.InvokePowerShellScript(ps1File);
21+
return [CommandBuilder.RegistryCommand(@$"add ""{rootKey}\{subKey}\Software\Microsoft\Windows\CurrentVersion\RunOnce"" /v ""UnattendedSetup"" /t REG_SZ /d ""{Escape(command)}"" /f")];
22+
})
23+
);
24+
}
25+
}

resource/RestartExplorer.ps1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Get-Process -Name 'explorer' -ErrorAction 'SilentlyContinue' | Where-Object -FilterScript {
2+
$_.SI -eq ( Get-Process -Id $PID ).SI;
3+
} | Stop-Process -Force;

resource/SetColorTheme.ps1

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,3 @@ Set-ItemProperty @params -Name 'SystemUsesLightTheme' -Value $lightThemeSystem;
77
Set-ItemProperty @params -Name 'AppsUseLightTheme' -Value $lightThemeApps;
88
Set-ItemProperty @params -Name 'ColorPrevalence' -Value $accentColorOnStart;
99
Set-ItemProperty @params -Name 'EnableTransparency' -Value $enableTransparency;
10-
11-
Start-Sleep -Seconds 10;
12-
Get-Process -Name 'explorer' -ErrorAction 'SilentlyContinue' | Where-Object -FilterScript {
13-
$_.SI -eq ( Get-Process -Id $PID ).SI;
14-
} | Stop-Process -Force;

resource/TaskbarIcons.ps1

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1 @@
1-
Remove-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband' -Name '*';
2-
Get-Process -Name 'explorer' -ErrorAction 'SilentlyContinue' | Where-Object -FilterScript {
3-
$_.SI -eq ( Get-Process -Id $PID ).SI;
4-
} | Stop-Process -Force;
1+
Remove-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband' -Name '*';

resource/TurnOffSystemSounds.ps1

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
New-PSDrive -PSProvider 'Registry' -Root 'HKEY_USERS' -Name 'HKU';
2-
$excludes = Get-ChildItem -LiteralPath "HKU:\${mountKey}\AppEvents\EventLabels" |
1+
$excludes = Get-ChildItem -LiteralPath "Registry::${mountKey}\AppEvents\EventLabels" |
32
Where-Object -FilterScript { ($_ | Get-ItemProperty).ExcludeFromCPL -eq 1; } |
43
Select-Object -ExpandProperty 'PSChildName';
5-
Get-ChildItem -Path "HKU:\${mountKey}\AppEvents\Schemes\Apps\*\*" |
4+
Get-ChildItem -Path "Registry::${mountKey}\AppEvents\Schemes\Apps\*\*" |
65
Where-Object -Property 'PSChildName' -NotIn $excludes |
7-
Get-ChildItem -Include '.Current' | Set-ItemProperty -Name '(default)' -Value '';
8-
Remove-PSDrive -Name 'HKU';
6+
Get-ChildItem -Include '.Current' | Set-ItemProperty -Name '(default)' -Value '';

0 commit comments

Comments
 (0)