Including winget in NTLite image for Post-Setup install

Kingells684

New Member
Hi All,

First time posting here, at my wits end trying to work out how to get this to work so hoping someone can share some knowledge.

I'm trying to make an NTLite image that automatically runs a Winget powershell script to install Chrome, Adobe Reader and Java post install on a Windows 10 ISO (and Windows 11 once I get it to work)

I tried to add the AppInstaller bunder using the W10_11StoreApps script created by another user on here (Sorry I forget their name) - When the image is created Winget does not work as it still believes the command is unrecognized in Powershell.

I tried to include a script that runs post setup in User context using Unattended setup (Ensuring OEM Setup Complete and Copy to boot image are ticked) - Nothing pops up and runs when this is setup, it just boots to windows like normal

Originally I wanted Windows to prompt you to create a user, not automatically create one as we wanted to set the password on build. I then changed it incase this was causing issues to create a windows account via NTLite but this also made no change.

I've also now included the latest online updates in the Integrate updates section, unfortunately this has made no difference either.

Nothing I seem to do is making these post setup scripts run, I've had success in the past using EXE's that silently install for Chrome, Reader and Java but these quickly become outdate and using Winget ensures that it's always the latest version being used as it's from the online Winget repository.

I've noticed that when the image is created, NO SetupComplete.cmd script is created in the sources\$OEM$\$$\Setup\Scripts location. I can see both the files I have created in the FilesU folder. (Unsure if this is correct or not)

Thinking it may be an issue with the scripts, I used PS2EXE converter and then integrated both EXE's into the post-setup section, again no luck there.

I also thought it may have been that by default the ExecutionPolicy on PC's will be set to restricted so I included a registry key to set this as unrestricted, I can see the regkey has worked as when checking the Execution Policy on PC's that have been built the policy is set to unrestricted. I've also ensured UAC prompts are disabled so this does not conflict with running programs in the post setup portion.

Hopefully that makes some sort of sense, let me know what files / info you need from me to investigate further if anyone can help!

Thanks in advance.
 

garlin

Moderator
Staff member
Looking at this problem, app provisioning is not being triggered for either Local SYSTEM or your user account. This means until that happens, winget.exe is not executable. There's many complaints filed under GitHub, but none of the workarounds succeeded in my testing.
 

Kingells684

New Member
Looking at this problem, app provisioning is not being triggered for either Local SYSTEM or your user account. This means until that happens, winget.exe is not executable. There's many complaints filed under GitHub, but none of the workarounds succeeded in my testing.
Damn okay, that's a pain. If you manually run the EXE's that I made using PS2EXE Winget installs and works fine though, I'm more confused as to why nothing I add to post-setup runs. There's no indiciation it's actually trying to run anything I add to post-setup when you login to the account after the build is complete. I've recreated the Windows installation NTLite uses a few times and still get nowhere... If I provide the files I use in post-setup could you test to see if they run for you?
 

Kingells684

New Member
Sure thing, attached is the Winget install ps1 and the Winget software ps1 in a zip file. I've also attached the Preset exported from the Image.

Thanks again,
Just post the preset, and the PS script that you compiled with PS2EXE.
 

Attachments

  • WingetScripts.rar
    620 bytes
  • WingetPreset.xml
    6.6 KB
Last edited:

garlin

Moderator
Staff member
Rewrote everything as one PS script, there's no real need for PS2EXE. NTLite executes all PS scripts using the bypass policy, so it's redundant to add the registry change unless you wanted it in general.

We have the same problem with winget 1.3 (RC); package installs but Appx provisioning doesn't happen. I followed the troubleshooting guide, and switching to Add-AppxProvisionedPackage made no difference. The question is how to force this without involving the Store.

Code:
$winget_URL = "https://github.com/microsoft/winget-cli/releases/download/v1.3.1872/Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"
$License_URL = "https://github.com/microsoft/winget-cli/releases/download/v1.3.1872/a941c144deac426082dc9f208f729138_License1.xml"
$VCLibs_URL = "https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx"

$AppsList = ('Google.Chrome', 'Adobe.Acrobat.Reader.32-bit', 'Oracle.JavaRuntimeEnvironment')

$TEMP = [Environment]::GetEnvironmentVariable('TEMP')

$winget_PackagePath = '{0}\{1}' -f $TEMP, ($winget_URL -split "/")[-1]
$License_Path = '{0}\{1}' -f $TEMP, ($License_URL -split "/")[-1]
$VCLibs_PackagePath = '{0}\{1}' -f $TEMP, ($VCLibs_URL -split "/")[-1]

Write-Host "winget is being downloaded"
try {
    Invoke-WebRequest -UseBasicParsing -Uri $winget_URL -OutFile $winget_PackagePath
    Invoke-WebRequest -UseBasicParsing -Uri $License_URL -OutFile $License_Path
    Invoke-WebRequest -UseBasicParsing -Uri $VCLibs_URL -OutFile $VCLibs_PackagePath
}
catch {
    $_.Exception
    exit 1
}
Write-Host "winget installer downloaded, adding package"

$null = Add-AppxProvisionedPackage -online -PackagePath $winget_PackagePath -LicensePath $License_Path -DependencyPackagePath $VCLibs_PackagePath

foreach ($package in $AppsList) {
    winget install -e --id $package --accept-source-agreements
}
 

abbodi86

Active Member
For online appx installation, Add-AppxPackage should follow Add-AppxProvisionedPackage
Code:
$null = Add-AppxProvisionedPackage -online -PackagePath $winget_PackagePath -LicensePath $License_Path -DependencyPackagePath $VCLibs_PackagePath
$null = Add-AppxPackage -Path $VCLibs_PackagePath
$null = Add-AppxPackage -Path $winget_PackagePath
 

garlin

Moderator
Staff member
Thanks, that worked!

After unblocking that problem, winget 1.3 now introduces a new dependency on UI.Xaml 2.7:
https://github.com/microsoft/winget-cli/issues/1861

One more rewrite to download UI.Xaml 2.7 nupkg and extract the Appx package inside, and this script will work independently of NTLite.
But it doesn't explain why image integration failed, unless it's the same stupid versioning problem where 2022 (retail) > 1 (RC).
 

garlin

Moderator
Staff member
I finished rewriting my script; it took over a week of testing to discover many normal steps didn't work in unattended Setup...

We'll install winget 1.3 development branch from GitHub (not Store), which introduces the new feature of downloading Store apps without using any Microsoft Account. Don't know if 22H2's next version will catch up to the dev branch.

Windows 10 x64-2022-07-23-22-22-22.pngWindows 10 x64-2022-07-23-22-35-15.png

1. Download PowerShell GUI for downloading Microsoft Store Apps, and select any version of Microsoft.DesktopAppInstaller.

We don't care about the actual DesktopAppInstaller package file, delete it now.
Look for the saved package files Microsoft.UI.Xaml.2.7 & Microsoft.VCLibs.140.00.UWPDesktop.

2. Download the latest winget-cli release from https://github.com/microsoft/winget-cli/releases.
UPDATE - winget 1.4 doesn't install Chrome, please use 1.3.1872

Click on the "Assets" arrow to reveal the file listings.
What we need are the MSIX bundle & matching License1.xml (it changes on every release).
Code:
a941c144deac426082dc9f208f729138_License1.xml
Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle

3. Edit the winget_install.ps1 script, and update your install list.
Code:
$winget_Apps = @(
    "Google.Chrome",
    "Adobe.Acrobat.Reader.32-bit",
    "Oracle.JavaRuntimeEnvironment"
)

4. Create a new $OEM$\$1\SetupWinget subfolder under the ISO mount directory's sources folder.

5. Copy these files to there (file versions will change):
Code:
a941c144deac426082dc9f208f729138_License1.xml
Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle
Microsoft.UI.Xaml.2.7_7.2207.21001.0_x64__8wekyb3d8bbwe.appx
Microsoft.VCLibs.140.00.UWPDesktop_14.0.30704.0_x64__8wekyb3d8bbwe.appx
winget_install.ps1

6. Add winget_install.ps1 to Post-Setup, Machine - Execution as Command:
Code:
powershell -NoProfile -ExecutionPolicy Bypass -File "C:\SetupWinget\winget_install.ps1"

For the long technical notes, read below.
Unwinding the different layers took a while. Post-Setup runs as SYSTEM, which is allowed to add provisioned packages but cannot trigger Add-AppxPackage -- since it's a Service Account. winget.exe exists at that point, but cannot run until the first user gets provisioned.

A solution proposed by David Just to force this action by scheduling an user task looks intriguing, but absolutely doesn't work for us.

Due to Windows' normal security model, unattended user tasks can't run in the background.
Assigning Logon as a batch job for users running unattended tasks

While we could change the security profile, it still doesn't work because we need the user's password. There's no way to handle this if you left the unattended file blank, and created a new user in OOBE Setup.

The answer would be split work across two tasks: SYSTEM runs Add-AppxProvisionedPackage, user runs Add-AppxPackage and winget is ready.
But now the problem is winget doesn't run elevated, and can't install anything.

What's the fix? Write a script which runs twice, once as SYSTEM and self-scheduling as local user. This solves the elevation problem and makes setup very simple with one file to manage.

Now the script's got a final problem, typing in PowerShell's window can interrupt it. Just don't touch it!
 

Attachments

  • winget_install.zip
    2.7 KB

Kingells684

New Member
Thankyou so much for your hard work, annoyingly though it still isn't playing ball for me.

I've built the image as per your steps above but I still can't get it to install the apps.

Initially I tried running the created ISO using Easy2boot but when you get to the desktop in Windows after running through the installer nothing happens.

I switched to just building a USB using Rufus from the NTLite ISO and I actually got some hope, it opened a CMD window on the 'Getting Ready' section of setup with the windows logo and spinning dots, it stayed on this with a blank CMD window for a while before dissapearing and putting me onto the desktop after logging in, again no apps installed and no extra scripts ran to install the apps post login.

The InstallWinget folder appears on the C drive with all the files inside. Running the script manually, I get an error saying "No MSFT_ScheduledTask objects with property 'TaskName' equal to 'winget'.


The steps I'm doing currently are as follows for the ISO setup:

Brand new ISO downloaded from media creation tool, extracted to folder using Winrar

Added the files and folders as specificed in step 4 above to 'Sources'

Loaded the image in NTLite, converting the WIM format as prompted

Settings Adjustments:
Fast Startup Disabled
UAC set to elevate without prompting

Unattended setup:
Local account setup with no password (Admin)
Localization settings all set to United Kingdon / en-GB
Time zone is UTC +0 Dublin, Edinburgh, Lisbon, London
Out-of-box experience, all set to skip, network location set to work
Skip auto activation
Send reports to MS True
Enable Dynamic Update True
Skip EULA

Copy to boot image ticked
OEM SetupComplete ticked

Post-Setup
Machine Execution, command,
powershell -NoProfile -ExecutionPolicy Bypass -File "C:\SetupWinget\winget_install.ps1"


Apply, create ISO

ISO loaded onto USB Using Rufus (Latest version 3.19)
Partition scheme is GPT, Target system UEFI non CSM

Booted to USB, deleted current partitions and installed windows as normal.

I've attached my XML if you can have a look and see if I'm doing anything obviously wrong? I cannot for the life of me work out why post-setup is not working at all no matter what I do. I've scoured the WinGet PS1 you provided and everything in it appears to work fine on the PC, the local admin command discovers the correct admin, the Winget command runs fine in windows when doing it manually, it's just not for some reason running. I'm more inclined to say it's something to do with the scheduled task not being created this time as it's not running Powershell upon login, instead of the Post-Setup commands not working.

I tried with / without a local user, without any unattended and still nothing.

I was trying to look for any Event's too in Event Viewer seeing the new event log commands sprinkled throughout but there's no events to show any issues annoyingly.

Thanks again for all your help.
 

Attachments

  • WinGet Preset.xml
    6.3 KB

garlin

Moderator
Staff member
Quick guess: UAC is set to elevate w/o prompting. Remove the "-RunLevel Highest":
Code:
    # Make a flawed assumption on the primary Local Admin
    $principal = New-ScheduledTaskPrincipal -UserId ((Get-LocalGroupMember -Group "Administrators")[-1].Name) -RunLevel Highest

The script should always work on a live system (even if nothing happens, because the packages or winget are already installed). Let me know what you see in the event logs as "winget", it writes whenever there's an execution error.
 

Kingells684

New Member
Gave it a try but still the same result I'm afraid, nothing running on login. I've checked event viewer logs under Application with the source selected as winget but there's no entries weirdly...

I'll try a blank build with no changes made to the Windows install apart from the script to see if it makes a difference.
 

garlin

Moderator
Staff member
After checking your preset, I realized OEM SetupComplete presents a special problem.

NTLite runs SetupComplete.cmd from Setup's specialize pass, which executes before any users are created. Thus we can't assign the elevated task to a normal user. No problem, we can split the PS script into two parts.

I have a working fix, but need to reconfirm before posting the OEM SetupComplete version.
 

Kingells684

New Member
Sounds good! I don't actually need to include SetupComplete in the build, I just remember seeing posts in the past saying it's good practice to enable as it'll force things to run. Let me try and build another ISO without SetupComplete and will report back.
 

garlin

Moderator
Staff member
Sounds good! I don't actually need to include SetupComplete in the build, I just remember seeing posts in the past saying it's good practice to enable as it'll force things to run. Let me try and build another ISO without SetupComplete and will report back.
Well, there has to be a solution written for OEM users since they don't have a choice.

We're back to splitting the script into two parts. My solution requires temporarily disabling UAC (EnableLUA), then re-enabling it when done.
This wasn't my first choice, but I don't see another simple workaround. If you don't want to restore the default UAC, then remove the last
Set-ItemProperty line from winget-OEMSetup_part2.ps1.

For OEM SetupComplete ONLY
Updated files are included in the last attachment.

Machine - Execution queue:
Code:
start cmd /c powershell -NoProfile -ExecutionPolicy Bypass -file "C:\SetupWinget\winget-OEMSetup_part1.ps1"
User - Execution queue:
Code:
powershell -NoProfile -ExecutionPolicy Bypass -file "C:\SetupWinget\winget-OEMSetup_part2.ps1"

Windows 10 x64-2022-08-04-08-48-22.png
 

Kingells684

New Member
Thanks Garlin! I actually managed to get it to work myself by adjusting the code slightly and changing it to run in User context rather than Machine context. I figured that the scheduled task wasn't being created so I removed that section of the code and then popped it in in user context. Upon testing 1st time it actually went through and worked straight off the bat! I've attached a copy of the script I used, I simply changed the command from Machine to user using the same switches and everything installed fine. I also added --force to the winget app install section as the Adobe installed complained about a hash mismatch and needed that added for it to proceed.
 

Attachments

  • winget_installv2.rar
    744 bytes

garlin

Moderator
Staff member
The original script doesn't run from User execution because winget doesn't inherit Admin rights. When you disable UAC, this is no longer a problem but also isn't recommended for most users since it breaks from the normal security model.

But happy you got it working!
 
Top