Search This Blog

Tuesday, 25 September 2018

Batch - Automate building Windows PE and optional components with a script

Install this language pack, then that...

Why?

Windows PE is invaluable for system maintenance and recovery. The process of creating bootable media is well documented by Microsoft. During this process, you have to add language packs and optional components if you wish for their functionality to be accessible in the final boot environment. Then you have to add the language packs to the image for the corresponding optional components for all installed language packs. Then you set the image language.
My bolding shows my point of disapproval... This is unacceptably long and complicated with a lot of boiler plate commands. All I wanted was two WinPE ISOs, one for amd64 and one for x86 with most optional components in EN-GB.

The process goes...

1: Copy the required architecture of PE from the ADK install location to a new place.
2: Mount the image into the already created mounted folder
3 (optional): set scratch space
4: Install the language pack you require
5: Install an optional component.
6: Install language pack for that component if they exist.
7: Repeat 6 for all installed languages
8: Repeat 5, 6 and 7 for all required components.
9: Set default language
10: Unmount the image
11: Create ISO from folder structure

Build it quick!

Put this in a text file, save it with a .bat extension and run as administrator to make both amd64 and x86 flavours of Windows PE (tested with WinPE 10) in folders 'WinPE10amd64' and 'WinPE10x86' on the system drive root with most features enabled and default language set to en-gb. It also creates a log file, opens it at the end and runs a PowerShell command to highlight any failed operations.

@echo off
call:IsAdmin
call "%SystemDrive%\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\DandISetEnv.bat"
call:CreatePE x86
call:CreatePE amd64

pause
goto:eof

:CreatePE
set OUTPE=%SystemDrive%\WinPE10%1

echo Copying Windows 10 PE %1 Files ...
call copype %1 "%OUTPE%" > "%SystemDrive%\PE%1_log.txt"

echo - Mounting image
Dism /Mount-Image /ImageFile:"%OUTPE%\media\sources\boot.wim" /index:1 /MountDir:"%OUTPE%\mount" >> "%SystemDrive%\PE%1_log.txt"

echo - Setting scratch space capacity
Dism /Set-ScratchSpace:512 /Image:"%OUTPE%\mount" >> "%SystemDrive%\PE%1_log.txt"

echo - Adding Language Packages
call:AddLP %1 en-us
call:AddLP %1 en-gb

echo - Applying Optional Components
call:AddOC %1 WinPE-MDAC
call:AddOC %1 WinPE-FMAPI
call:AddOC %1 WinPE-EnhancedStorage
call:AddOC %1 WinPE-WDS-Tools
call:AddOC %1 WinPE-RNDIS
call:AddOC %1 WinPE-Dot3Svc
call:AddOC %1 WinPE-GamingPeripherals
call:AddOC %1 WinPE-WinReCfg
call:AddOC %1 WinPE-WMI
call:AddOC %1 WinPE-SecureStartup
call:AddOC %1 WinPE-NetFX
call:AddOC %1 WinPE-Scripting
call:AddOC %1 WinPE-PowerShell
call:AddOC %1 WinPE-DismCmdlets
call:AddOC %1 WinPE-SecureBootCmdlets
call:AddOC %1 WinPE-StorageWMI
call:AddOC %1 WinPE-PlatformID

echo - Setting Language
Dism /Set-AllIntl:en-GB /Image:"%OUTPE%\mount" >> "%SystemDrive%\PE%1_log.txt"

echo - Committing changes
Dism /Unmount-Image /MountDir:"%OUTPE%\mount" /commit >> "%SystemDrive%\PE%1_log.txt"

echo Creating ISO ...
call MakeWinPEMedia /ISO "%OUTPE%" "%OUTPE%.iso"

start notepad "%SystemDrive%\PE%1_log.txt"
start powershell -EP bypass -NoE -Command "Select-String -Path '%SystemDrive%\PE%1_log.txt' -Pattern 'operation'"

echo Process complete.
goto:eof

:AddOC
echo -- Applying %2
Dism /Add-Package /Image:"%SystemDrive%\WinPE10%1\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\%1\WinPE_OCs\%2.cab" >> "%SystemDrive%\PE%1_log.txt"
set l=en-us
if exist "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\%1\WinPE_OCs\%l%\%2_%l%.cab" (
echo --- Applying language pack for %l%
Dism /Add-Package /Image:"%SystemDrive%\WinPE10%1\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\%1\WinPE_OCs\%l%\%2_%l%.cab" >> "%SystemDrive%\PE%1_log.txt"
)
set l=en-gb
if exist "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\%1\WinPE_OCs\%l%\%2_%l%.cab" (
echo --- Applying language pack for %l%
Dism /Add-Package /Image:"%SystemDrive%\WinPE10%1\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\%1\WinPE_OCs\%l%\%2_%l%.cab" >> "%SystemDrive%\PE%1_log.txt"
)
goto:eof

:AddLP
echo -- Applying language pack for %2
Dism /Add-Package /Image:"%SystemDrive%\WinPE10%1\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\%1\WinPE_OCs\%2\lp.cab" >> "%SystemDrive%\PE%1_log.txt"
goto:eof

:IsAdmin
%systemroot%\system32\Reg.exe query "HKU\S-1-5-19\Environment"
If Not %ERRORLEVEL% EQU 0 (
Cls & Echo You must have administrator rights to continue ...
Pause & Exit
)
Cls
goto:eof

Powershell - Reading INI files into PSObjects for easy handling

"But I want it that way..."

Why INI?

I've embraced the .NET reliance in PowerShell, regex handling, direct member calls and dot notation. I really like using PowerShell (and worth the price - ISESteroids) to develop scripts for managing the 800+ computers I maintain. From bulk domain management to full system deployments with faster results than MDT, PowerShell can handle it all. If you're working with deploying applications and creating deployments, handling INI files becomes inevitable and it's worth reading the fairly simple structure of an INI file being that they are so often used in installers, setup files and others.  Other files, such as INF, take the INI structure further and provide more complex functionality, but there are many old and new technologies still using INIs as configuration files and some of the  Microsoft Windows system files are in INI format.

My Interest in this

Handling files with a text editor is faster when working with one or two files, but if you need to handle hundreds of copies distributed across computers or folders, then this becomes tedious. I'm a huge fan and advocate of PortableApps.com for having a suite of tools on a USB stick. The folder structure for this comprises of a few INI files and I wanted a quick way to reference this information in a script so I could remove all the games.

There are already ways

In the search to find a Google solution, I discovered this Microsoft Scripting Guy post that describes how to feed a file, line by line through PowerShell's switch function with the -File parameter and provides links to more complete scripts which, with much respect to Oliver Lipkau, gave me the base idea and regex strings.

Tweak, tweak...

A few things were lacking in the solution that I really wanted to change:

 - The returned object is a HashTable
I embrace the PowerShell over .Net so a raw hash table although may be faster than other methods, can be more cumbersome to handle in code with try catch statements and reading the object in the console requires additional discovery before querying values. I also think this looks ugly and doesn't leverage the dot notation or autocomplete of PowerShell.

 - The returned values are out of order when exported
The sections and values of the INI are loaded into a HashTable which does not retain structure order when writing back to the file (given the functions supplied). This can be particularly irritating for manual editors who like to keep the order of comments, sections and values.

So I rewrote the PowerShell Functions for a little more flavour.
Results were:
 - PowerShell 2.0 support
 - Verbose is noisy, SilentlyContinue is quiet

For the Import:
 - Accepts path input from pipeline (absolute paths)
- Full Line Comments are noted in a ";Comment#" NoteProperty
- Inline Comments are noted in a ";InlineComment#" NoteProperty preceding the property or section
- Errors and warnings based on $ErrorActionPreference
- The Object returned is a PSCustomObject with dot notation accessible settable properties.
- Quotation translation (" and ') is performed.
- Multi line (line ending in ' \') is supported by switch

For the Export:
- Values, Sections and Comments are to be written back into the file in the same order. 
- Confirmation will be shown on attempted overwrite, but not on append, unless forced.
- Encoding option can be selected from default set.
- Switches to enclose the section names, property names and values in quotaion marks

How to use:

Import-INIToPSObject:
$example = Import-INIToPSObject -Path C:\Temp\test.ini
Then let PowerShell do the autocomplete magic:


Listing a section:


Modifying Values:

Export-PSObjectToINI:
$example | Export-PSObjectToINI -Path C:\Temp\test.ini

Sometimes you're modifying, sometimes you're creating files on the fly. If the latter is the case, you can create a new INI Object by defining a structure like this:
[pscustomobject]@{
  rootProp1='test1'
  rootProp2=$null
  section1=[pscustomobject]@{
    sectProp1=$true
    sectProp2=4
  }
  section2=[pscustomobject]@{
    sectProp1=$false
    sectProp2=''
  }
}|Export-PSObjectToINI -Path 'C:\temp\test.ini'

Not as pretty but... CODE!

The code is now available on the PowerShell Gallery!

Wednesday, 6 June 2018

PowerShell Tip - Did the script run as an administrator (Elevated)?

No frills...

Nice and simple one liner for top of scripts to quickly stop 'forgot to elevate' mistakes on quick solutions:
if(-not((New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator))){Write-Host 'Failed to run as Administrator.';Pause;Exit}