From 596bca0b4ede4e1601f6e62aee5c6a0288a6550d Mon Sep 17 00:00:00 2001
From: Manuel Thalmann <m@nuth.ch>
Date: Wed, 21 Aug 2024 18:34:18 +0200
Subject: [PATCH] Streamline the creation of users

---
 scripts/Common/Scripts/Config.ps1 |  89 ++++++++++++++++++++-
 scripts/Windows/OS/Install.ps1    | 126 +++++++++++++-----------------
 scripts/Windows/Scripts/Users.ps1 | 110 +++++++++++++++++++-------
 3 files changed, 227 insertions(+), 98 deletions(-)

diff --git a/scripts/Common/Scripts/Config.ps1 b/scripts/Common/Scripts/Config.ps1
index 60fca018..e7c56140 100644
--- a/scripts/Common/Scripts/Config.ps1
+++ b/scripts/Common/Scripts/Config.ps1
@@ -9,12 +9,20 @@ enum SetupStage {
     Configure
     Install
     CreateUser
-    ConfigureUser
+}
+
+enum UserStage {
+    Create
+    Configure
+    Completed
 }
 
 $null = New-Module {
     [string] $configRoot = "HKLM:\Software\PortValhalla";
     [string] $stageOption = "Stage";
+    [string] $userOption = "SetupUser";
+    [string] $userStageOption = "UserStage";
+    [string] $accountOption = "MSAccount";
     [string] $finishedOption = "Finished";
     [RegistryKey] $key = $null;
 
@@ -273,6 +281,85 @@ $null = New-Module {
         $null = Set-SetupOption $stageOption $Name;
     }
 
+    <#
+        .SYNOPSIS
+        Gets the current user to set up.
+    #>
+    function Get-CurrentUser {
+        return (Get-SetupOption $userOption) ?? 0;
+    }
+
+    <#
+        .SYNOPSIS
+        Sets the index of the current user to set up.
+
+        .PARAMETER Value
+        The index of the user to set up.
+    #>
+    function Set-CurrentUser {
+        param(
+            [int] $Value
+        )
+
+        Set-SetupOption $userOption $value;
+    }
+
+    <#
+        .SYNOPSIS
+        Gets the name of the current stage of the user setup.
+    #>
+    function Get-UserStage {
+        $stage = Get-SetupOption $userStageOption;
+
+        if ($null -ne $stage) {
+            $stage = [UserStage]$stage;
+        }
+
+        return $stage;
+    }
+
+    <#
+        .SYNOPSIS
+        Sets the current stage of the user setup.
+
+        .PARAMETER Name
+        The name of the stage to set.
+    #>
+    function Set-UserStage {
+        param(
+            $Name
+        )
+
+        if (-not (($null -eq $Name) -or ($Name -is [string]))) {
+            $Name = ([UserStage]$Name).ToString();
+        }
+
+        $null = Set-SetupOption $userStageOption $Name;
+    }
+
+    <#
+        .SYNOPSIS
+        Gets the name of the microsoft account to create.
+    #>
+    function Get-MSAccountName {
+        return Get-SetupOption $accountOption;
+    }
+
+    <#
+        .SYNOPSIS
+        Sets the name of the microsoft account to create.
+
+        .PARAMETER Name
+        The name of the microsoft account to create.
+    #>
+    function Set-MSAccountName {
+        param(
+            [string] $Name
+        )
+
+        Set-SetupOption $accountOption $Name;
+    }
+
     <#
         .SYNOPSIS
         Gets a value indicating whether the setup has finished.
diff --git a/scripts/Windows/OS/Install.ps1 b/scripts/Windows/OS/Install.ps1
index a3ce7631..afabab06 100644
--- a/scripts/Windows/OS/Install.ps1
+++ b/scripts/Windows/OS/Install.ps1
@@ -196,6 +196,9 @@ $null = New-Module {
 
                             Start-OneShot {
                                 switch (Get-OneShotTask) {
+                                    ([OneShotTask]::InitializeMSAccount) {
+                                        Initialize-UserCreation;
+                                    }
                                     ([OneShotTask]::DisableUAC) {
                                         Disable-UAC;
                                         Register-Setup;
@@ -536,87 +539,70 @@ $null = New-Module {
                                     Set-Stage ([SetupStage]::CreateUser);
                                 }
                                 ([SetupStage]::CreateUser) {
-                                    Start-ValhallaUserSetup;
-                                    Set-Stage ([SetupStage]::ConfigureUser);
-                                }
-                                ([SetupStage]::ConfigureUser) {
-                                    $userOption = "CurrentUser";
+                                    $users = @(Get-Users);
+                                    $i = Get-CurrentUser;
 
-                                    function Get-CurrentUser {
-                                        (Get-SetupOption $userOption) ?? 0;
-                                    }
+                                    for (; $i -lt $users.Count; $i++) {
+                                        $name = $users[$i];
+                                        $msAccount = Get-UserConfig -UserName $name "microsoftAccount";
+                                        Set-CurrentUser $i;
 
-                                    function Set-CurrentUser {
-                                        param([int] $Value)
-                                        Set-SetupOption $userOption $Value;
-                                    }
+                                        if (Test-Admin) {
+                                            Disable-BootMessage;
+                                        }
 
-                                    [string[]] $users = Get-Users;
-                                    $currentUser = Get-CurrentUser;
-
-                                    if (Test-Admin) {
-                                        Disable-BootMessage;
-                                    }
-
-                                    if ($currentUser -lt $users.Count) {
-                                        $user = Get-LocalUser $users[$currentUser];
-                                        $msAccount = Get-UserConfig -UserName "$user" -Name "microsoftAccount";
-
-                                        $adminGroup = @{
-                                            SID = [SecurityIdentifier]::new([WellKnownSidType]::BuiltinAdministratorsSid, $null);
-                                        };
-
-                                        Add-LocalGroupMember `
-                                            @adminGroup `
-                                            $user `
-                                            -ErrorAction SilentlyContinue;
-
-                                        if ($env:UserName -ne "$user") {
-                                            Disable-LocalUser $env:UserName;
-                                            Enable-LocalUser $user;
-
-                                            if ($msAccount) {
-                                                Enable-UAC;
-                                                Disable-Autologin;
-                                                Enable-OneShotListener;
-                                                Set-BootMessage -Caption "Please Log In" -Message "Please log in using your new Microsoft Account ``$user``.";
-                                            } else {
-                                                Set-AutologinUser "$user";
-                                            }
-
-                                            Restart-Intermediate -DefaultUser;
-                                            return;
-                                        } else {
-                                            $configure = {
-                                                Deploy-SoftwareAction -Action ([InstallerAction]::ConfigureUser);
-                                                Remove-LocalGroupMember -Member "$user" @adminGroup -ErrorAction SilentlyContinue;
-
-                                                foreach ($group in Get-UserConfig -UserName "$user" "groups") {
-                                                    Add-LocalGroupMember -Member "$user" -Name "$group";
+                                        while ((Get-UserStage) -ne ([UserStage]::Completed)) {
+                                            switch (Get-UserStage) {
+                                                ($null) {
+                                                    Set-UserStage ([UserStage]::Create);
+                                                    continue;
                                                 }
-                                            }
+                                                ([UserStage]::Create) {
 
-                                            if ($msAccount) {
-                                                if (-not (Test-Admin)) {
-                                                    Invoke-OneShot DisableUAC;
-                                                    Restart-Computer;
-                                                    return;
-                                                } else {
-                                                    & $configure;
-                                                    Clear-SetupRegistration;
-                                                    Disable-OneShotListener;
+                                                    if ($env:UserName -ne $name) {
+                                                        New-ValhallaUser $name;
+
+                                                        if ($msAccount) {
+                                                            Register-Setup -DefaultUser;
+                                                            logoff;
+                                                        } else {
+                                                            Restart-Intermediate;
+                                                        }
+
+                                                        exit;
+                                                    } else {
+                                                        if ($msAccount) {
+                                                            if (-not (Test-Admin)) {
+                                                                Invoke-OneShot DisableUAC;
+                                                                Restart-Computer;
+                                                                return;
+                                                            }
+
+                                                            Clear-SetupRegistration;
+                                                            Disable-OneShotListener;
+                                                        }
+
+                                                        Set-UserStage ([UserStage]::Configure);
+                                                    }
+                                                }
+                                                (([UserStage]::Configure)) {
+                                                    $adminGroup = @{
+                                                        SID = [SecurityIdentifier]::new([WellKnownSidType]::BuiltinAdministratorsSid, $null);
+                                                    };
+
+                                                    Deploy-SoftwareAction -Action ([InstallerAction]::ConfigureUser);
+                                                    Remove-LocalGroupMember -Member $name @adminGroup -ErrorAction SilentlyContinue;
+
+                                                    foreach ($group in Get-UserConfig -UserName $name "groups") {
+                                                        Add-LocalGroupMember -Member $name -Name "$group";
+                                                    }
                                                 }
-                                            } else {
-                                                & $configure;
                                             }
                                         }
 
                                         if (-not $msAccount) {
-                                            net user "$user" /logonpasswordchg:yes;
+                                            net user $name /logonpasswordchg:yes;
                                         }
-
-                                        Set-CurrentUser ($currentUser + 1);
-                                        continue;
                                     }
 
                                     Set-IsFinished $true;
diff --git a/scripts/Windows/Scripts/Users.ps1 b/scripts/Windows/Scripts/Users.ps1
index 32b41924..6d9a38ca 100644
--- a/scripts/Windows/Scripts/Users.ps1
+++ b/scripts/Windows/Scripts/Users.ps1
@@ -1,15 +1,23 @@
 using namespace System.Management.Automation.Host;
+using namespace System.Security.Principal;
 
 $null = New-Module {
     . "$PSScriptRoot/../../Common/Scripts/Config.ps1";
-    [string] $userOption = "SetupUser";
+    . "$PSScriptRoot/../../Common/Scripts/Operations.ps1";
+    . "$PSScriptRoot/../../Common/Types/OneShotTask.ps1";
+    $loggedInUserOption = "LoggedInUser";
 
     <#
         .SYNOPSIS
-        Creates the configured users.
+        Creates a new user for the PortValhalla setup.
+
+        .PARAMETER Name
+        The name of the user to create.
     #>
-    function Start-ValhallaUserSetup {
-        [string[]] $users = Get-Users;
+    function New-ValhallaUser {
+        param(
+            [string] $Name
+        )
 
         function Add-MicrosoftAccount {
             param(
@@ -26,7 +34,7 @@ $null = New-Module {
                             "Thus, you have to do it by yourself.",
                             "So sorry…") -join "`n");
 
-                    Write-Host "Create a user for ``$Name`` manually (because Windows is too stupid)…";
+                    Write-Host "Create a user for ``$Name`` manually… (because Windows is too stupid)";
                     $null = Read-Host "Hit enter once you're done";
 
                     $newUsers = @(Get-LocalUser | Where-Object { -not ($currentUsers -contains $_.Name) });
@@ -57,7 +65,6 @@ $null = New-Module {
 
                                         for ($i = 0; $i -lt $newUsers.Count; $i++) {
                                             $name = "$($newUsers[$i])";
-
                                             [ChoiceDescription]::new("&$($i + 1) - ``$name``", "Your user is ``$name``");
                                         }
                                     }), 0);
@@ -74,33 +81,82 @@ $null = New-Module {
                 }
             };
 
-            Write-Host "Renaming the new user to ``$Name``…";
-            Rename-LocalUser $newUser $Name;
+            Set-MSAccountName ([string]$newUser);
         }
 
-        for ($i = 0; $i -lt $users.Count; $i++) {
-            Set-SetupOption $userOption $i;
-            $name = $users[$i];
-            Write-Host "Creating personal user ``$name``…";
-            $displayName = Get-UserConfig -UserName $name "displayName";
+        $msAccount = Get-UserConfig -UserName $Name "microsoftAccount";
 
-            $userArguments = @{
-                name = $name;
-            };
-
-            if ($displayName) {
-                $userArguments.fullName = $displayName;
+        if ($msAccount) {
+            if (Test-Admin) {
+                Write-Host "Preparing environment for creating MS Account";
+                Enable-OneShotListener;
+                Enable-UAC;
+                Restart-Intermediate -CurrentUser;
+                exit;
             }
+        }
 
-            if (Get-UserConfig -UserName $name "microsoftAccount") {
-                Add-MicrosoftAccount $name;
-            } else {
-                New-LocalUser -Disabled -NoPassword @userArguments;
-                Set-LocalUser $name -PasswordNeverExpires $true;
-                Set-LocalUser $name -PasswordNeverExpires $false;
-            }
+        Write-Host "Creating personal user ``$Name``…";
 
-            Set-LocalUser @userArguments;
+        if ($msAccount) {
+            Add-MicrosoftAccount $Name;
+            Set-SetupOption $loggedInUserOption $env:UserName;
+            Invoke-OneShot ([OneShotTask]::InitializeMSAccount);
+        } else {
+            New-LocalUser -NoPassword $Name;
+            Set-LocalUser $Name -PasswordNeverExpires $true;
+            Set-LocalUser $Name -PasswordNeverExpires $false;
+            Initialize-UserCreation;
+        }
+    }
+
+    <#
+        .SYNOPSIS
+        Prepares the first login for initializing the current user under configuration.
+    #>
+    function Initialize-UserCreation {
+        $name = (@(Get-Users))[(Get-CurrentUser)];
+        $msAccount = Get-UserConfig -UserName $name "microsoftAccount";
+        $displayName = Get-UserConfig -UserName $Name "displayName";
+
+        Write-Host "Initializing user ``$name``…";
+
+        $userArguments = @{
+            name = $name;
+        };
+
+        if ($displayName) {
+            $userArguments.fullName = $displayName;
+        }
+
+        $adminGroup = @{
+            SID = [SecurityIdentifier]::new([WellKnownSidType]::BuiltinAdministratorsSid, $null);
+        };
+
+        if ($msAccount) {
+            $accountName = Get-MSAccountName;
+            Write-Host "Renaming ``$accountName`` to ``$name``…"
+            Rename-LocalUser $accountName $name;
+        }
+
+        Set-LocalUser @userArguments;
+
+        if ($msAccount) {
+            Disable-LocalUser (Get-SetupOption $loggedInUserOption);
+        } else {
+            Disable-LocalUser $env:UserName;
+        }
+
+        Add-LocalGroupMember `
+            @adminGroup `
+            $name `
+            -ErrorAction SilentlyContinue;
+
+        if ($msAccount) {
+            Disable-Autologin;
+            Set-BootMessage -Caption "Please Log In" -Message "Please log in using your new Microsoft Account ``$name``.";
+        } else {
+            Set-AutologinUser "$name";
         }
     }
 };