Add files via upload

This commit is contained in:
freedbygrace
2020-11-06 20:32:00 -05:00
committed by GitHub
parent 357501c3dd
commit db5414b438
12 changed files with 1187 additions and 0 deletions
+575
View File
@@ -0,0 +1,575 @@
#Requires -Version 3
<#
.SYNOPSIS
This script performs the automated adding 1 or more features on demand (FODs) to a currently running operating system, a mounted windows image, or an image that has been deployed to a volume while in WindowsPE.
.DESCRIPTION
This script allows the features on demand to be added to images intended to be used for in-place upgrades and/or bare metal deployment so that the features are enabled before the operating system has even booted for the first time.
This script will also save headaches fighting with Windows Update in domain environments that enforce WSUS which causes errors. In short, these commands were really built for consumer environments so this script is born out of that limitation.
.PARAMETER Online
Informs the script to add capabililties to the currently running operating system.
.PARAMETER Offline
Informs the script to add capabilities to a mounted windows image or a windows image that has been expanded onto a disk, such as during MDT or SCCM operating system deployment.
.PARAMETER ImagePath
The directory path to the mounted windows image or the drive letter of a windows image that has been expanded onto a disk while in WindowsPE.
.PARAMETER CapabilitiesToAdd
A valid regular expression. Allows to enable mutiple capabilities by using a regular expression.
Example: .*NetFX3.*|^SNMP.*Client.*|.*WMI.*SNMP.*Provider.*Client.*
Meaning: Anything with "NetFX3" in the capability name; Anything that begins with SNMP and has the word "Client" in the capability name; Anything that has the words WMI, SNMP, Provider, and Client in the capability name.
.PARAMETER Source
1 or more valid folder paths. These locations must contain the valid feature on demand files that are relevant to your operating system, release ID, and architecture. Example: Windows 10 1809 X64
Functionality exists in this script to make this easier. If you look at the folder "WindowsCapabilities" included with this script, and create a folder path in there like the following, they will be automatically found by the script.
ScriptPath\WindowsCapabilities\1809\X64\<Place Feature On Demand Files Here>
.PARAMETER LogDir
A valid folder path. If the folder does not exist, it will be created. This parameter can also be specified by the alias "LogPath".
.PARAMETER ContinueOnError
Ignore failures.
.EXAMPLE
powershell.exe -ExecutionPolicy Bypass -NoProfile -NoLogo -File "%FolderPathContainingScript%\Add-WindowsCapabilities.ps1" -Online -Source "MyFODSourcePath" -FODs ".*NetFX3.*|^SNMP.*Client.*|.*WMI.*SNMP.*Provider.*Client.*"
.EXAMPLE
powershell.exe -ExecutionPolicy Bypass -NoProfile -NoLogo -File "%FolderPathContainingScript%\Add-WindowsCapabilities.ps1" -Offline -ImagePath "$($Env:SystemDrive):\YourWindowsImageMountPath" -FODs ".*NetFX3.*|^SNMP.*Client.*|.*WMI.*SNMP.*Provider.*Client.*"
.NOTES
Unfortunately you cannot just copy various files out of the features on demand ISO and expect the powershell commands to work. Some features on demand have other dependency file(s), etc.
and all of this is figured out when trying to install the feature. You need to use dism.exe (There is not a powershell native equivalent command currently) and use the following code snippet to export the appropriate files for this process to work.
###Begin Dism.exe example command output###
/Export-Source {/CapabilityName:<name_in_image> | /Recipe:<path_to_recipe_file>}
/Source:<source> /Target:<target> [/IncludeImageCapabilities]
Export a set of capabilities into a new repository.
Use the /CapabilityName to specify the capability you would like to
export. Multiple /CapabilityName arguments can be used. You can use /Recipe
instead of /CapabilityName to specify multiple capabilities at a time.
Use the /Source argument to specify the location of the source repository.
Use the /Target to specify the location of the new repository.
Use the /IncludeImageCapabilities to export image capabilities into the
new repository.
Examples:
Dism /Image:C:\test\offline /Export-Source /Source:C:\test\source
/Target:C:\test\target /CapabilityName:Language.Basic~~~en-US~0.0.1.0
Dism /Image:C:\test\offline /Export-Source /Source:C:\test\source
/Target:C:\test\target /Recipe:C:\test\recipe\recipe.xml
###End Dism.exe example command output###
###Begin Code Snippet###
###Make sure to download the Feature On Demand (FOD) iso from the Microsoft Volume Licensing Service Center before beginning.###
###https://www.microsoft.com/Licensing/servicecenter/Home.aspx###
###This is the root path of contents of the Features On Demand (FOD) iso (You could also mount the ISO and specify the drive letter)
[System.IO.DirectoryInfo]$FODSourcePath = "E:\Deployment\ISOs\FeaturesOnDemand\SW_DVD9_NTRL_Win_10_2004_64Bit_MultiLang_FOD_1_X22-21311"
###This is where you want the files to be exported, the directory will be created if it does not exist###
[System.IO.DirectoryInfo]$FODExportPath = "$($Env:SystemDrive)\FeaturesOnDemand\2004"
If ($FODExportPath.Exists -eq $False) {$Null = [System.IO.Directory]::CreateDirectory($FODExportPath.FullName)}
[System.IO.FileInfo]$WIMPath = "D:\install.wim"
[System.IO.DirectoryInfo]$WIMMountPath = "$($Env:Temp.TrimEnd('\'))\$([System.GUID]::NewGUID().ToString().ToUpperInvariant())"
If ($WIMMountPath.Exists -eq $False) {$Null = [System.IO.Directory]::CreateDirectory($WIMMountPath.FullName)}
Import-Module DISM -Force
$WindowsImageProperties = Get-WindowsImage -ImagePath "$($WIMPath.FullName)" | Where-Object {($_.ImageName -imatch "^.*Enterprise$")}
Mount-WindowsImage -ImagePath "$($WIMPath.FullName)" -Path "$($WIMMountPath.FullName)" -Index "$($WindowsImageProperties.ImageIndex)" -ReadOnly
[System.IO.FileInfo]$BinaryPath = "$([System.Environment]::SystemDirectory)\dism.exe"
[String[]]$CapabilityNames = @()
$CapabilityNames += "/CapabilityName:`"Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0`""
$CapabilityNames += "/CapabilityName:`"NetFX3~~~~`""
$CapabilityNames += "/CapabilityName:`"WMI-SNMP-Provider.Client~~~~0.0.1.0`""
$CapabilityNames += "/CapabilityName:`"SNMP.Client~~~~0.0.1.0`""
[String]$CapabilityNamesAsString = $CapabilityNames -Join ' '
[System.IO.FileInfo]$LogPath = "$($Env:Temp.TrimEnd('\'))\$($BinaryPath.BaseName)_ExportFODSource.log"
[String]$BinaryParameters = "/Image:`"$($WIMMountPath.FullName)`" /Export-Source /Source:`"$($FODSourcePath.FullName)`" /Target:`"$($FODExportPath.FullName)`" $($CapabilityNamesAsString) /LogPath:`"$($LogPath.FullName)`""
[System.IO.FileInfo]$BinaryStandardOutputPath = "$($Env:Temp.TrimEnd('\'))\$($BinaryPath.BaseName)_StandardOutput.log"
[System.IO.FileInfo]$BinaryStandardErrorPath = "$($Env:Temp.TrimEnd('\'))\$($BinaryPath.BaseName)_StandardError.log"
Write-Verbose -Message "Binary Path: $($BinaryPath.FullName)" -Verbose
Write-Verbose -Message "Binary Parameters: $($BinaryParameters)" -Verbose
Write-Verbose -Message "Standard Output Path: $($BinaryStandardOutputPath.FullName)" -Verbose
Write-Verbose -Message "Standard Error Path: $($BinaryStandardErrorPath.FullName)" -Verbose
$ExecuteBinary = Start-Process -FilePath "$($BinaryPath.FullName)" -ArgumentList "$($BinaryParameters)" -WindowStyle Hidden -RedirectStandardOutput "$($BinaryStandardOutputPath)" -RedirectStandardError "$($BinaryStandardErrorPath)" -Wait -PassThru
Write-Verbose -Message "Binary Exit Code = $($ExecuteBinary.ExitCode)" -Verbose
Dismount-WindowsImage -Path "$($WIMMountPath.FullName)" -Discard
###End Code Snippet###
.LINK
https://docs.microsoft.com/en-us/powershell/module/dism/get-windowscapability?view=win10-ps
.LINK
https://docs.microsoft.com/en-us/powershell/module/dism/add-windowscapability?view=win10-ps
.LINK
https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/features-on-demand-v2--capabilities
.LINK
https://forums.mydigitallife.net/threads/how-to-export-a-set-of-capabilities-into-a-new-repository-better-use-ready-fod-iso.80739/
.LINK
https://p0w3rsh3ll.wordpress.com/2019/05/24/quick-post-dism-and-features-on-demand-fod/
#>
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$False, ParameterSetName="Online")]
[Switch]$Online,
[Parameter(Mandatory=$False, ParameterSetName="Offline")]
[Switch]$Offline,
[Parameter(Mandatory=$False, ParameterSetName="Offline")]
[ValidateNotNullOrEmpty()]
[ValidateScript({($_ -imatch '^[a-zA-Z][\:]\\{1,}$') -or ($_ -imatch '^[a-zA-Z][\:]\\.*?[^\\]$')})]
[System.IO.DirectoryInfo]$ImagePath,
[Parameter(Mandatory=$False)]
[ValidateNotNullOrEmpty()]
[Alias('FODs')]
[Regex]$CapabilitiesToAdd = '.*NetFX3.*|^SNMP.*Client.*|.*WMI.*SNMP.*Provider.*Client.*',
[Parameter(Mandatory=$False)]
[ValidateNotNullOrEmpty()]
[ValidateScript({(($_ -imatch '^[a-zA-Z][\:]\\$') -or ($_ -imatch '^[a-zA-Z][\:]\\.*$')) -and (Test-Path -Path $_.FullName)})]
[System.IO.DirectoryInfo[]]$Source,
[Parameter(Mandatory=$False)]
[ValidateNotNullOrEmpty()]
[ValidateScript({($_ -imatch '^[a-zA-Z][\:]\\.*?[^\\]$') -or ($_ -imatch "^\\(?:\\[^<>:`"/\\|?*]+)+$")})]
[System.IO.DirectoryInfo]$LogDir,
[Parameter(Mandatory=$False)]
[Switch]$ContinueOnError
)
#Define Default Action Preferences
$Script:DebugPreference = 'SilentlyContinue'
$Script:ErrorActionPreference = 'Stop'
$Script:VerbosePreference = 'SilentlyContinue'
$Script:WarningPreference = 'Continue'
$Script:ConfirmPreference = 'None'
#Load WMI Classes
$Baseboard = Get-WmiObject -Namespace "root\CIMv2" -Class "Win32_Baseboard" -Property * | Select-Object -Property *
$Bios = Get-WmiObject -Namespace "root\CIMv2" -Class "Win32_Bios" -Property * | Select-Object -Property *
$ComputerSystem = Get-WmiObject -Namespace "root\CIMv2" -Class "Win32_ComputerSystem" -Property * | Select-Object -Property *
$OperatingSystem = Get-WmiObject -Namespace "root\CIMv2" -Class "Win32_OperatingSystem" -Property * | Select-Object -Property *
#Retrieve property values
$OSArchitecture = $($OperatingSystem.OSArchitecture).Replace("-bit", "").Replace("32", "86").Insert(0,"x").ToUpper()
#Define variable(s)
$DateTimeLogFormat = 'dddd, MMMM dd, yyyy hh:mm:ss tt' ###Monday, January 01, 2019 10:15:34 AM###
[ScriptBlock]$GetCurrentDateTimeLogFormat = {(Get-Date).ToString($DateTimeLogFormat)}
$DateTimeFileFormat = 'yyyyMMdd_hhmmsstt' ###20190403_115354AM###
[ScriptBlock]$GetCurrentDateTimeFileFormat = {(Get-Date).ToString($DateTimeFileFormat)}
[System.IO.FileInfo]$ScriptPath = "$($MyInvocation.MyCommand.Definition)"
[System.IO.DirectoryInfo]$ScriptDirectory = "$($ScriptPath.Directory.FullName)"
[System.IO.DirectoryInfo]$FunctionsDirectory = "$($ScriptDirectory.FullName)\Functions"
[System.IO.DirectoryInfo]$ModulesDirectory = "$($ScriptDirectory.FullName)\Modules"
[System.IO.DirectoryInfo]$ToolsDirectory = "$($ScriptDirectory.FullName)\Tools"
[System.IO.DirectoryInfo]$ToolsDirectoryGeneric = "$($ScriptDirectory.FullName)\Tools\All"
[System.IO.DirectoryInfo]$ToolsDirectoryArchSpecific = "$($ScriptDirectory.FullName)\Tools\$($OSArchitecture)"
$IsWindowsPE = Test-Path -Path 'HKLM:\SYSTEM\ControlSet001\Control\MiniNT' -ErrorAction SilentlyContinue
#Log any useful information
$LogMessage = "IsWindowsPE = $($IsWindowsPE.ToString())`r`n"
Write-Verbose -Message "$($LogMessage)" -Verbose
$LogMessage = "Script Path = $($ScriptPath.FullName)`r`n"
Write-Verbose -Message "$($LogMessage)" -Verbose
$LogMessage = "Script Directory = $($ScriptDirectory.FullName)`r`n"
Write-Verbose -Message "$($LogMessage)" -Verbose
#Log task sequence variables if debug mode is enabled within the task sequence
Try
{
[System.__ComObject]$TSEnvironment = New-Object -ComObject "Microsoft.SMS.TSEnvironment"
If ($TSEnvironment -ine $Null)
{
$IsRunningTaskSequence = $True
}
}
Catch
{
$IsRunningTaskSequence = $False
}
#Determine the default logging path if the parameter is not specified and is not assigned a default value
If (($PSBoundParameters.ContainsKey('LogDir') -eq $False) -and ($LogDir -ieq $Null))
{
If ($IsRunningTaskSequence -eq $True)
{
[String]$_SMSTSLogPath = "$($TSEnvironment.Value('_SMSTSLogPath'))"
If ([String]::IsNullOrEmpty($_SMSTSLogPath) -eq $False)
{
[System.IO.DirectoryInfo]$TSLogDirectory = "$($_SMSTSLogPath)"
}
Else
{
[System.IO.DirectoryInfo]$TSLogDirectory = "$($Env:Windir)\Temp\SMSTSLog"
}
[System.IO.DirectoryInfo]$LogDir = "$($TSLogDirectory.FullName)\$($ScriptPath.BaseName)"
}
ElseIf ($IsRunningTaskSequence -eq $False)
{
[System.IO.DirectoryInfo]$LogDir = "$($Env:Windir)\Logs\Software\$($ScriptPath.BaseName)"
}
}
#Start transcripting (Logging)
Try
{
[System.IO.FileInfo]$ScriptLogPath = "$($LogDir.FullName)\$($ScriptPath.BaseName)_$($GetCurrentDateTimeFileFormat.Invoke()).log"
If ($ScriptLogPath.Directory.Exists -eq $False) {[Void][System.IO.Directory]::CreateDirectory($ScriptLogPath.Directory.FullName)}
Start-Transcript -Path "$($ScriptLogPath.FullName)" -IncludeInvocationHeader -Force -Verbose
}
Catch
{
If ([String]::IsNullOrEmpty($_.Exception.Message)) {$ExceptionMessage = "$($_.Exception.Errors.Message)"} Else {$ExceptionMessage = "$($_.Exception.Message)"}
$ErrorMessage = "[Error Message: $($ExceptionMessage)][ScriptName: $($_.InvocationInfo.ScriptName)][Line Number: $($_.InvocationInfo.ScriptLineNumber)][Line Position: $($_.InvocationInfo.OffsetInLine)][Code: $($_.InvocationInfo.Line.Trim())]"
Write-Error -Message "$($ErrorMessage)"
}
#Log any useful information
$LogMessage = "IsWindowsPE = $($IsWindowsPE.ToString())"
Write-Verbose -Message "$($LogMessage)" -Verbose
$LogMessage = "Script Path = $($ScriptPath.FullName)"
Write-Verbose -Message "$($LogMessage)" -Verbose
$DirectoryVariables = Get-Variable | Where-Object {($_.Value -ine $Null) -and ($_.Value -is [System.IO.DirectoryInfo])}
ForEach ($DirectoryVariable In $DirectoryVariables)
{
$LogMessage = "$($DirectoryVariable.Name) = $($DirectoryVariable.Value.FullName)"
Write-Verbose -Message "$($LogMessage)" -Verbose
}
#region Import Dependency Modules
$Modules = Get-Module -Name "$($ModulesDirectory.FullName)\*" -ListAvailable -ErrorAction Stop
$ModuleGroups = $Modules | Group-Object -Property @('Name')
ForEach ($ModuleGroup In $ModuleGroups)
{
$LatestModuleVersion = $ModuleGroup.Group | Sort-Object -Property @('Version') -Descending | Select-Object -First 1
If ($LatestModuleVersion -ine $Null)
{
$LogMessage = "Attempting to import dependency powershell module `"$($LatestModuleVersion.Name) [Version: $($LatestModuleVersion.Version.ToString())]`". Please Wait..."
Write-Verbose -Message "$($LogMessage)" -Verbose
Import-Module -Name "$($LatestModuleVersion.Path)" -Global -DisableNameChecking -Force -ErrorAction Stop
}
}
#endregion
#region Dot Source Dependency Scripts
#Dot source any additional script(s) from the functions directory. This will provide flexibility to add additional functions without adding complexity to the main script and to maintain function consistency.
Try
{
If ($FunctionsDirectory.Exists -eq $True)
{
[String[]]$AdditionalFunctionsFilter = "*.ps1"
$AdditionalFunctionsToImport = Get-ChildItem -Path "$($FunctionsDirectory.FullName)" -Include ($AdditionalFunctionsFilter) -Recurse -Force | Where-Object {($_ -is [System.IO.FileInfo])}
$AdditionalFunctionsToImportCount = $AdditionalFunctionsToImport | Measure-Object | Select-Object -ExpandProperty Count
If ($AdditionalFunctionsToImportCount -gt 0)
{
ForEach ($AdditionalFunctionToImport In $AdditionalFunctionsToImport)
{
Try
{
$LogMessage = "Attempting to dot source dependency script `"$($AdditionalFunctionToImport.Name)`". Please Wait...`r`n`r`nScript Path: `"$($AdditionalFunctionToImport.FullName)`""
Write-Verbose -Message "$($LogMessage)" -Verbose
. "$($AdditionalFunctionToImport.FullName)"
}
Catch
{
$ErrorMessage = "[Error Message: $($_.Exception.Message)]`r`n`r`n[ScriptName: $($_.InvocationInfo.ScriptName)]`r`n[Line Number: $($_.InvocationInfo.ScriptLineNumber)]`r`n[Line Position: $($_.InvocationInfo.OffsetInLine)]`r`n[Code: $($_.InvocationInfo.Line.Trim())]"
Write-Error -Message "$($ErrorMessage)" -Verbose
}
}
}
}
}
Catch
{
$ErrorMessage = "[Error Message: $($_.Exception.Message)]`r`n`r`n[ScriptName: $($_.InvocationInfo.ScriptName)]`r`n[Line Number: $($_.InvocationInfo.ScriptLineNumber)]`r`n[Line Position: $($_.InvocationInfo.OffsetInLine)]`r`n[Code: $($_.InvocationInfo.Line.Trim())]"
Write-Error -Message "$($ErrorMessage)" -Verbose
}
#endregion
#region Load Dependencies
If ($IsWindowsPE -eq $True)
{
$DLLsToLoad = Get-ChildItem -Path "$($ToolsDirectoryGeneric.FullName)" -Filter '*.dll' -Recurse -Force | Where-Object {($_ -is [System.IO.FileInfo])}
ForEach ($DLLToLoad In $DLLsToLoad)
{
$LogMessage = "Attempting to load DLL module `"$($DLLToLoad.FullName)`". Please Wait..."
Write-Verbose -Message "$($LogMessage)" -Verbose
$AssemblyBytes = [System.IO.File]::ReadAllBytes($DLLToLoad.FullName)
$AssemblyBytesBase64 = [System.Convert]::ToBase64String($AssemblyBytes)
$Null = [System.Reflection.Assembly]::Load([System.Convert]::FromBase64String($AssemblyBytesBase64))
}
}
#endregion
#Perform script action(s)
Try
{
#Tasks defined within this block will only execute if a task sequence is running
If (($IsRunningTaskSequence -eq $True))
{
If (($PSBoundParameters.ContainsKey('ImagePath') -eq $False) -and ($ImagePath -eq $Null))
{
[System.IO.DirectoryInfo]$ImagePath = "$($TSEnvironment.Value('OSDisk'))\"
}
}
$LogMessage = "Parameter Set Name = $($PSCmdlet.ParameterSetName.ToString())"
Write-Verbose -Message "$($LogMessage)" -Verbose
If (($PSCmdlet.ParameterSetName -imatch "Online") -and ($IsWindowsPE -eq $False))
{
[String]$XReleaseID = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name "ReleaseID").ReleaseID
$XEditionID = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name "EditionID").EditionID
$XBuildLabEX = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name "BuildLabEX").BuildLabEX
$XCurrentBuildNumber = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name "CurrentBuildNumber").CurrentBuildNumber
$WindowsImageVersion = $OperatingSystem.Version
}
ElseIf ($PSCmdlet.ParameterSetName -imatch "Offline")
{
[System.IO.FileInfo]$RegistryHivePath = "$($ImagePath.FullName.TrimEnd('\'))\Windows\System32\Config\Software"
$LogMessage = "Registry Hive Path = $($RegistryHivePath.FullName)"
Write-Verbose -Message "$($LogMessage)" -Verbose
$RegistryHive = [Registry.RegistryHiveOnDemand]::New($RegistryHivePath.FullName)
$RegistryValues = @()
[String[]]$RegistryHiveKeyPaths = 'Root\Microsoft\Windows NT\CurrentVersion'
ForEach ($RegistryHiveKeyPath In $RegistryHiveKeyPaths)
{
$RegistryKeyPathValues = $RegistryHive.GetKey($RegistryHiveKeyPath).Values
$RegistryValues += ($RegistryKeyPathValues)
}
ForEach ($RegistryValue In $RegistryValues)
{
Try
{
If ($RegistryValue.ValueName.ToString().Trim() -imatch "^ReleaseID$|^EditionID$|^BuildLabEX$|^CurrentBuildNumber$")
{
$VariableName = "X$($RegistryValue.ValueName.ToString().Trim())"
$VariableDescription = "Contains the value of `"$($VariableName)`" for the windows image applied to `"$($ImagePath.FullName)`""
Switch ($RegistryValue.ValueType)
{
{$_ -imatch 'RegSZ'} {$VariableValue = "$($RegistryValue.ValueData.ToString().Trim())"}
{$_ -imatch 'RegDword'} {$VariableValue = [System.Convert]::ToString("0x$($RegistryValue.ValueData)", 10)}
{$_ -imatch 'RegQword'} {$VariableValue = [System.Convert]::ToInt64(($RegistryValue.ValueData), 16)}
}
$CreateVariableFromRegistryValue = New-Variable -Name "$($VariableName)" -Value ($VariableValue) -Description "$($VariableDescription)" -Force -Verbose
}
}
Catch
{
If ([String]::IsNullOrEmpty($_.Exception.Message)) {$ExceptionMessage = "$($_.Exception.Errors.Message)"} Else {$ExceptionMessage = "$($_.Exception.Message)"}
$WarningMessage = "[Error Message: $($ExceptionMessage)][ScriptName: $($_.InvocationInfo.ScriptName)][Line Number: $($_.InvocationInfo.ScriptLineNumber)][Line Position: $($_.InvocationInfo.OffsetInLine)][Code: $($_.InvocationInfo.Line.Trim())]"
Write-Error -Message "$($WarningMessage)" -Verbose
}
}
[System.IO.FileInfo]$WindowsImageCommandPromptPath = "$($ImagePath.FullName.TrimEnd('\'))\Windows\System32\cmd.exe"
[Version]$WindowsImageVersion = "$($WindowsImageCommandPromptPath.VersionInfo.ProductVersionRaw.Major).$($WindowsImageCommandPromptPath.VersionInfo.ProductVersionRaw.Minor).$($XCurrentBuildNumber)"
}
#Tasks defined here will execute whether a task sequence is running or not
$LogMessage = "Image Version = $($WindowsImageVersion.ToString())"
Write-Verbose -Message "$($LogMessage)" -Verbose
$LogMessage = "Image Release ID = $($XReleaseID)"
Write-Verbose -Message "$($LogMessage)" -Verbose
$LogMessage = "Image Edition = $($XEditionID)"
Write-Verbose -Message "$($LogMessage)" -Verbose
Switch ($XBuildLabEX)
{
{$_ -inotmatch '.*amd64.*'} {$XWindowsImageArchitecture = 'X86'}
{$_ -imatch '.*amd64.*'} {$XWindowsImageArchitecture = 'X64'}
}
$LogMessage = "Image Architecture = $($XWindowsImageArchitecture)"
Write-Verbose -Message "$($LogMessage)" -Verbose
If (($PSBoundParameters.ContainsKey('Source') -eq $False) -and ($Source -ieq $Null))
{
[System.IO.DirectoryInfo[]]$Source = @()
$Source += "$($ScriptDirectory.FullName)\WindowsCapabilities\$($XReleaseID)\$($XWindowsImageArchitecture)"
}
$LogMessage = "Source = $($Source.FullName)"
Write-Verbose -Message "$($LogMessage)" -Verbose
If (($PSCmdlet.ParameterSetName -imatch "Online") -and ($IsWindowsPE -eq $False))
{
[Hashtable]$GetWindowsCapabilityParameters = @{
Online = [Switch]::Present;
LimitAccess = [Switch]::Present
}
[Hashtable]$AddWindowsCapabilityParameters = @{
Online = [Switch]::Present;
Source = ($Source.FullName);
LimitAccess = [Switch]::Present;
LogLevel = "Errors";
Verbose = [Switch]::Present
}
}
ElseIf ($PSCmdlet.ParameterSetName -imatch "Offline")
{
[Hashtable]$GetWindowsCapabilityParameters = @{
Path = "$($ImagePath.FullName)";
LimitAccess = [Switch]::Present
}
[Hashtable]$AddWindowsCapabilityParameters = @{
Path = "$($ImagePath.FullName)";
Source = ($Source.FullName);
LimitAccess = [Switch]::Present;
LogLevel = "Errors";
Verbose = [Switch]::Present
}
}
$LogMessage = "Attempting to determine the available windows capabilities. Please Wait..."
Write-Verbose -Message "$($LogMessage)" -Verbose
$WindowsCapabilities = Get-WindowsCapability @GetWindowsCapabilityParameters | Select-Object -Property @('*') | Sort-Object -Property @('Name')
$WindowsCapabilitiesToAdd = $WindowsCapabilities | Where-Object {($_.Name -imatch $CapabilitiesToAdd.ToString())}
$WindowsCapabilitiesToAddCount = $WindowsCapabilitiesToAdd | Measure-Object | Select-Object -ExpandProperty Count
$Counter = 1
ForEach ($WindowsCapabilityToAdd In $WindowsCapabilitiesToAdd)
{
If ($WindowsCapabilityToAdd.State -inotmatch 'Installed')
{
Try
{
[Int]$ProgressID = 1
[String]$ActivityMessage = "Add-WindowsCapability $($WindowsCapabilityToAdd.Name) [State: $($WindowsCapabilityToAdd.State)]"
[String]$StatusMessage = "Add-WindowsCapability $($WindowsCapabilityToAdd.Name) [State: $($WindowsCapabilityToAdd.State)] ($($Counter.ToString()) of $($WindowsCapabilitiesToAddCount.ToString()))"
[Int]$PercentComplete = (($Counter / $WindowsCapabilitiesToAddCount) * 100)
$LogMessage = "$($StatusMessage). Please Wait..."
Write-Verbose -Message "$($LogMessage)" -Verbose
Write-Progress -ID ($ProgressID) -Activity ($ActivityMessage) -Status ($StatusMessage) -PercentComplete ($PercentComplete)
[System.IO.FileInfo]$WindowsCapabilityToAddLogPath = "$($LogDir.FullName)\$($WindowsCapabilityToAdd.Name).log"
If ($WindowsCapabilityToAddLogPath.Directory.Exists -eq $False) {$Null = [System.IO.Directory]::CreateDirectory($WindowsCapabilityToAddLogPath.Directory.FullName)}
$LogMessage = "Log Path = `"$($WindowsCapabilityToAddLogPath.FullName)`""
Write-Verbose -Message "$($LogMessage)" -Verbose
$AddWindowsCapability = Add-WindowsCapability @AddWindowsCapabilityParameters -Name "$($WindowsCapabilityToAdd.Name)" -LogPath "$($WindowsCapabilityToAddLogPath.FullName)"
If ($? -eq $True)
{
$LogMessage = "Addition of the `"$($WindowsCapabilityToAdd.Name)`" windows capability was successful!"
Write-Verbose -Message "$($LogMessage)" -Verbose
}
ElseIf ($? -eq $False)
{
$LogMessage = "Addition of the `"$($WindowsCapabilityToAdd.Name)`" windows capability was unsuccessful!"
Write-Error -Message "$($LogMessage)" -Verbose
}
}
Catch
{
If ([String]::IsNullOrEmpty($_.Exception.Message)) {$ExceptionMessage = "$($_.Exception.Errors.Message)"} Else {$ExceptionMessage = "$($_.Exception.Message)"}
$WarningMessage = "[Error Message: $($ExceptionMessage)][ScriptName: $($_.InvocationInfo.ScriptName)][Line Number: $($_.InvocationInfo.ScriptLineNumber)][Line Position: $($_.InvocationInfo.OffsetInLine)][Code: $($_.InvocationInfo.Line.Trim())]"
Write-Error -Message "$($WarningMessage)" -Verbose
}
}
ElseIf ($WindowsCapabilityToAdd.State -imatch 'Installed')
{
$LogMessage = "[Windows Capability ($($Counter.ToString()) of $($WindowsCapabilitiesToAddCount.ToString()))] - `"$($WindowsCapabilityToAdd.Name)`" [State: $($WindowsCapabilityToAdd.State)]. No further action will be taken."
Write-Verbose -Message "$($LogMessage)" -Verbose
}
$Counter++
}
#Tasks defined here will execute whether only if a task sequence is not running
If ($IsRunningTaskSequence -eq $False)
{
$WarningMessage = "There is no task sequence running.`r`n"
Write-Warning -Message "$($WarningMessage)" -Verbose
}
#Stop transcripting (Logging)
Try
{
Stop-Transcript -Verbose
}
Catch
{
If ([String]::IsNullOrEmpty($_.Exception.Message)) {$ExceptionMessage = "$($_.Exception.Errors.Message)"} Else {$ExceptionMessage = "$($_.Exception.Message)"}
$ErrorMessage = "[Error Message: $($ExceptionMessage)][ScriptName: $($_.InvocationInfo.ScriptName)][Line Number: $($_.InvocationInfo.ScriptLineNumber)][Line Position: $($_.InvocationInfo.OffsetInLine)][Code: $($_.InvocationInfo.Line.Trim())]"
Write-Error -Message "$($ErrorMessage)"
}
}
Catch
{
If ([String]::IsNullOrEmpty($_.Exception.Message)) {$ExceptionMessage = "$($_.Exception.Errors.Message -Join "`r`n`r`n")"} Else {$ExceptionMessage = "$($_.Exception.Message)"}
$ErrorMessage = "[Error Message: $($ExceptionMessage)]`r`n`r`n[ScriptName: $($_.InvocationInfo.ScriptName)]`r`n[Line Number: $($_.InvocationInfo.ScriptLineNumber)]`r`n[Line Position: $($_.InvocationInfo.OffsetInLine)]`r`n[Code: $($_.InvocationInfo.Line.Trim())]`r`n"
If ($ContinueOnError.IsPresent -eq $False) {Throw "$($ErrorMessage)"}
}
@@ -0,0 +1 @@
###
+1
View File
@@ -0,0 +1 @@
###
@@ -0,0 +1 @@
###
Binary file not shown.
+564
View File
@@ -0,0 +1,564 @@
$Script:TaskSequenceEnvironment = $null
$Script:TaskSequenceProgressUi = $null
function Confirm-TSEnvironmentSetup()
{
<#
.SYNOPSIS
Verifies the TSEnvironment Com Object is initiated into an object.
.DESCRIPTION
Verifies the TSEnvironment Com Object is initiated into an object.
.INPUTS
None
.OUTPUTS
None
.NOTE
This module can be used statically to initiate the TSEnvironment module, however, simply running one of the commands will initate it for you.
#>
if ($Script:TaskSequenceEnvironment -eq $null)
{
try
{
$Script:TaskSequenceEnvironment = New-Object -ComObject Microsoft.SMS.TSEnvironment
}
catch
{
throw "Unable to connect to the Task Sequence Environment! Please verify you are in a running Task Sequence Environment.`n`nErrorDetails:`n$_"
}
}
}
function Get-TSVariables()
{
<#
.SYNOPSIS
Get all Task Sequence Variable Names
.DESCRIPTION
Returns a string array of all Task Sequence Variable Names (not values).
.INPUT
None
.OUTPUTS
String[]
.EXAMPLE
$arrayOfVariableNames = Get-TSVariables
#>
Confirm-TSEnvironmentSetup
$allVar = @()
foreach ($variable in $Script:TaskSequenceEnvironment.GetVariables())
{
$allVar += $variable
}
return $allVar
}
function Get-TSValue()
{
<#
.SYNOPSIS
Get a Task Sequence Variables Value
.DESCRIPTION
Obtains the value of a specific Task Sequence Variable.
.PARAMETER Name
Specifies the variable name to resolve.
.INPUTS
String
.OUTPUTS
String
.EXAMPLE
$OsdComputerName = Get-TSValue -Name "OSDComputerName"
#>
param(
[Parameter(Mandatory=$true)]
[string] $Name
)
Confirm-TSEnvironmentSetup
return $Script:TaskSequenceEnvironment.Value($Name)
}
function Set-TSVariable()
{
<#
.SYNOPSIS
Set or Create a Task Sequence Variables Value
.DESCRIPTION
Sets or Creates a Task Sequence Variables Value.
Will return a boolean value indicating success or failure.
.PARAMETER Name
Specifies the variable name to set or create.
.PARAMETER Value
Specifies the variable value
.INPUTS
- Name: String
- Value: System.Boolean
.OUTPUTS
System.Boolean
.EXAMPLE
Sets the OSDComputerName task sequence variable to "MyComputer123"
$didSet = Set-TSVariable -Name "OSDComputerName" -Value "MyComputer123"
.EXAMPLE
Creates a new Task Sequence Variable, and sets its value
$didSet = Set-TSVariable -Name "MyNewVar" -Value 'MyNewVarsValue"
#>
param(
[Parameter(Mandatory=$true)]
[string] $Name,
[Parameter(Mandatory=$true)]
[string] $Value
)
Confirm-TSEnvironmentSetup
try
{
$Script:TaskSequenceEnvironment.Value($Name) = $Value
return $true
}
catch
{
return $false
}
}
function Get-TSAllValues()
{
<#
.SYNOPSIS
Gets all Task Sequence Variables and Values
.DESCRIPTION
Gets all Task Sequence Variables with their associated values.
.INPUTS
None
.OUTPUTS
System.Object
.EXAMPLE
$TSVariableObject = Get-TSAllValues
#>
Confirm-TSEnvironmentSetup
$Values = New-Object -TypeName System.Object
foreach ($Variable in $Script:TaskSequenceEnvironment.GetVariables())
{
$Values | Add-Member -MemberType NoteProperty -Name $Variable -Value "$($Script:TaskSequenceEnvironment.Value($Variable))"
}
return $Values
}
function Confirm-TSProgressUISetup()
{
<#
.SYNOPSIS
Verifies the TSProgresUI Com Object is initiated into an object.
.DESCRIPTION
Verifies the TSProgresUI Com Object is initiated into an object.
.INPUTS
None
.OUTPUTS
None
.NOTE
This module can be used statically but is intended to be used by other functions.
#>
if ($Script:TaskSequenceProgressUi -eq $null)
{
try
{
$Script:TaskSequenceProgressUi = New-Object -ComObject Microsoft.SMS.TSProgressUI
}
catch
{
throw "Unable to connect to the Task Sequence Progress UI! Please verify you are in a running Task Sequence Environment. Please note: TSProgressUI cannot be loaded during a prestart command.`n`nErrorDetails:`n$_"
}
}
}
function Show-TSActionProgress()
{
<#
.SYNOPSIS
Shows task sequence secondary progress of a specific step
.DESCRIPTION
Adds a second progress bar to the existing Task Sequence Progress UI.
This progress bar can be updated to allow for a real-time progress of
a specific task sequence sub-step.
The Step and Max Step parameters are calculated when passed. This allows
you to have a "max steps" of 400, and update the step parameter. 100%
would be achieved when step is 400 and max step is 400. The percentages
are calculated behind the scenes by the Com Object.
.PARAMETER Message
The message to display the progress
.PARAMETER Step
Integer indicating current step
.PARAMETER MaxStep
Integer indicating 100%. A number other than 100 can be used.
.INPUTS
- Message: String
- Step: Long
- MaxStep: Long
.OUTPUTS
None
.EXAMPLE
Set's "Custom Step 1" at 30 percent complete
Show-TSActionProgress -Message "Running Custom Step 1" -Step 100 -MaxStep 300
.EXAMPLE
Set's "Custom Step 1" at 50 percent complete
Show-TSActionProgress -Message "Running Custom Step 1" -Step 150 -MaxStep 300
.EXAMPLE
Set's "Custom Step 1" at 100 percent complete
Show-TSActionProgress -Message "Running Custom Step 1" -Step 300 -MaxStep 300
#>
param(
[Parameter(Mandatory=$true)]
[string] $Message,
[Parameter(Mandatory=$true)]
[long] $Step,
[Parameter(Mandatory=$true)]
[long] $MaxStep
)
Confirm-TSProgressUISetup
Confirm-TSEnvironmentSetup
$Script:TaskSequenceProgressUi.ShowActionProgress(`
$Script:TaskSequenceEnvironment.Value("_SMSTSOrgName"),`
$Script:TaskSequenceEnvironment.Value("_SMSTSPackageName"),`
$Script:TaskSequenceEnvironment.Value("_SMSTSCustomProgressDialogMessage"),`
$Script:TaskSequenceEnvironment.Value("_SMSTSCurrentActionName"),`
[Convert]::ToUInt32($Script:TaskSequenceEnvironment.Value("_SMSTSNextInstructionPointer")),`
[Convert]::ToUInt32($Script:TaskSequenceEnvironment.Value("_SMSTSInstructionTableSize")),`
$Message,`
$Step,`
$MaxStep)
}
function Close-TSProgressDialog()
{
<#
.SYNOPSIS
Hides the Task Sequence Progress Dialog
.DESCRIPTION
Hides the Task Sequence Progress Dialog
.INPUTS
None
.OUTPUTS
None
.EXAMPLE
Close-TSProgressDialog
#>
Confirm-TSProgressUISetup
$Script:TaskSequenceProgressUi.CloseProgressDialog()
}
function Show-TSProgress()
{
<#
.SYNOPSIS
Shows task sequence progress of a specific step
.DESCRIPTION
Manipulates the Task Sequence progress UI; top progress bar only.
This progress bar can be updated to allow for a real-time progress of
a specific task sequence step.
The Step and Max Step parameters are calculated when passed. This allows
you to have a "max steps" of 400, and update the step parameter. 100%
would be achieved when step is 400 and max step is 400. The percentages
are calculated behind the scenes by the Com Object.
.PARAMETER CurrentAction
Step Title. Modifies the "Running action: " Message
.PARAMETER Step
Integer indicating current step
.PARAMETER MaxStep
Integer indicating 100%. A number other than 100 can be used.
.INPUTS
- CurrentAction: String
- Step: Long
- MaxStep: Long
.OUTPUTS
None
.EXAMPLE
Set's "Custom Step 1" at 30 percent complete
Show-TSProgress -CurrentAction "Running Custom Step 1" -Step 100 -MaxStep 300
.EXAMPLE
Set's "Custom Step 1" at 50 percent complete
Show-TSProgress -CurrentAction "Running Custom Step 1" -Step 150 -MaxStep 300
.EXAMPLE
Set's "Custom Step 1" at 100 percent complete
Show-TSProgress -CurrentAction "Running Custom Step 1" -Step 300 -MaxStep 300
#>
param(
[Parameter(Mandatory=$true)]
[string] $CurrentAction,
[Parameter(Mandatory=$true)]
[long] $Step,
[Parameter(Mandatory=$true)]
[long] $MaxStep
)
Confirm-TSProgressUISetup
Confirm-TSEnvironmentSetup
$Script:TaskSequenceProgressUi.ShowTSProgress(`
$Script:TaskSequenceEnvironment.Value("_SMSTSOrgName"), `
$Script:TaskSequenceEnvironment.Value("_SMSTSPackageName"), `
$Script:TaskSequenceEnvironment.Value("_SMSTSCustomProgressDialogMessage"), `
$CurrentAction, `
$Step, `
$MaxStep)
}
function Show-TSErrorDialog()
{
<#
.SYNOPSIS
Shows the Task Sequence Error Dialog
.DESCRIPTION
Shows a task sequence error dialog allowing for custom failure pages.
.PARAMETER OrganizationName
Name of your Organization
.PARAMETER CustomTitle
Custom Error Title
.PARAMETER ErrorMessage
Message details of the error
.PARAMETER ErrorCode
Error Code the Task sequence will exit with
.PARAMETER TimeoutInSeconds
Timout for the Reboot Prompt
.PARAMETER ForceReboot
Indicates whether a reboot will be forced or not
.INPUTS
- OrganizationName: String
- CustomTitle: String
- ErrorMessage: String
- ErrorCode: Long
- TimeoutInSeconds: Long
- ForceReboot: System.Boolean
.OUTPUTS
None
.EXAMPLE
Sets an Error but does not force a reboot
Show-TSErrorDialog -OrganizationName "My Organization" -CustomTitle "An Error occured during the things" -ErrorMessage "That thing you tried...it didnt work" -ErrorCode 123456 -TimeoutInSeconds 90 -ForceReboot $false
.EXAMPLE
Sets an Error and forces a reboot
Show-TSErrorDialog -OrganizationName "My Organization" -CustomTitle "An Error occured during the things" -ErrorMessage "He's dead Jim!" -ErrorCode 123456 -TimeoutInSeconds 90 -ForceReboot $true
#>
param(
[Parameter(Mandatory=$true)]
[string] $OrganizationName,
[Parameter(Mandatory=$true)]
[string] $CustomTitle,
[Parameter(Mandatory=$true)]
[string] $ErrorMessage,
[Parameter(Mandatory=$true)]
[long] $ErrorCode,
[Parameter(Mandatory=$true)]
[long] $TimeoutInSeconds,
[Parameter(Mandatory=$true)]
[bool] $ForceReboot
)
Confirm-TSProgressUISetup
Confirm-TSEnvironmentSetup
if ($ForceReboot)
{
$Script:TaskSequenceProgressUi.ShowErrorDialog($OrganizationName, $Script:TaskSequenceEnvironment.Value("_SMSTSPackageName"), $CustomTitle, $ErrorMessage, $ErrorCode, $TimeoutInSeconds, 1)
}
else
{
$Script:TaskSequenceProgressUi.ShowErrorDialog($OrganizationName, $Script:TaskSequenceEnvironment.Value("_SMSTSPackageName"), $CustomTitle, $ErrorMessage, $ErrorCode, $TimeoutInSeconds, 0)
}
}
function Show-TSMessage()
{
<#
.SYNOPSIS
Shows a Windows Forms Message Box
.DESCRIPTION
Shows a Windows Forms Message Box, but does not return the response.
This will halt any current operations while the prompt is shown.
.PARAMETER Message
Message to be displayed
.PARAMETER Title
Title of the message box
.PARAMETER Type
Button Style for the MessageBox
0 = OK
1 = OK, Cancel
2 = Abort, Retry, Ignore
3 = Yes, No, Cancel
4 = Yes, No
5 = Retry, Cancel
6 = Cancel, Try Again, Continue
.INPUTS
- Message: String
- Title: String
- Type: Long
.OUTPUTS
None
.EXAMPLE
Sets an Error but does not force a reboot
Show-TSErrorDialog -OrganizationName "My Organization" -CustomTitle "An Error occured during the things" -ErrorMessage "That thing you tried...it didnt work" -ErrorCode 123456 -TimeoutInSeconds 90 -ForceReboot $false
.EXAMPLE
Sets an Error and forces a reboot
Show-TSErrorDialog -OrganizationName "My Organization" -CustomTitle "An Error occured during the things" -ErrorMessage "He's dead Jim!" -ErrorCode 123456 -TimeoutInSeconds 90 -ForceReboot $true
#>
param(
[Parameter(Mandatory=$true)]
[string] $Message,
[Parameter(Mandatory=$true)]
[string] $Title,
[Parameter(Mandatory=$true)]
[ValidateRange(0,6)]
[long] $Type
)
Confirm-TSProgressUISetup
$Script:TaskSequenceProgressUi.ShowMessage($Message, $Title, $Type)
}
function Show-TSRebootDialog()
{
<#
.SYNOPSIS
Shows the Reboot Dialog
.DESCRIPTION
Shows the Task Sequence "System Restart" Dialog. This allows you
to trigger custom Task Sequence Reboot Messages.
.PARAMETER OrganizationName
Name of your Organization
.PARAMETER CustomTitle
Custom Title for the Reboot Dialog
.PARAMETER Message
Detailed Message regarding the reboot
.PARAMETER TimeoutInSeconds
Timout before the system reboots
.INPUTS
- OrganizationName: String
- CustomTitle: String
- Message: String
- TimeoutInSeconds: Long
.OUTPUTS
None
.EXAMPLE
Show's a Reboot Dialog
Show-TSRebootDialog -OrganizationName "My Organization" -CustomTitle "I need a reboot!" -Message "I need to reboot to complete something..." -TimeoutInSeconds 90
#>
param(
[Parameter(Mandatory=$true)]
[string] $OrganizationName,
[Parameter(Mandatory=$true)]
[string] $CustomTitle,
[Parameter(Mandatory=$true)]
[string] $Message,
[Parameter(Mandatory=$true)]
[long] $TimeoutInSeconds
)
Confirm-TSProgressUISetup
Confirm-TSEnvironmentSetup
$Script:TaskSequenceProgressUi.ShowRebootDialog($OrganizationName, $Script:TaskSequenceEnvironment.Value("_SMSTSPackageName"), $CustomTitle, $Message, $TimeoutInSeconds)
}
function Show-TSSwapMediaDialog()
{
<#
.SYNOPSIS
Shows Task Sequence Swap Media Dialog.
.DESCRIPTION
Shows Task Sequence Swap Media Dialog.
.PARAMETER TaskSequenceName
Name of the Task Sequence
.PARAMETER MediaNumber
Media Number to insert
.INPUTS
- TaskSequenceName: String
- CustomTitle: Long
.OUTPUTS
None
.EXAMPLE
Prompts to insert media #2 for the Task Sequence "My Task Sequence"
Show-TSSwapMediaDialog -TaskSequenceName "My Task Sequence" -MediaNumber 2
#>
param(
[Parameter(Mandatory=$true)]
[string] $TaskSequenceName,
[Parameter(Mandatory=$true)]
[long] $MediaNumber
)
Confirm-TSProgressUISetup
$Script:TaskSequenceProgressUi.ShowSwapMediaDialog($TaskSequenceName, $MediaNumber)
}
#Encode a plain text string to a Base64 string
Function ConvertTo-Base64
{
[CmdletBinding(SupportsShouldProcess=$False)]
Param
(
[Parameter(Mandatory=$True, ValueFromPipeline=$True)]
[ValidateNotNullOrEmpty()]
[String]$String
)
$EncodedString = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($String))
Write-Verbose -Message "$($NewLine)`"$($String)`" has been converted to the following Base64 encoded string `"$($EncodedString)`"$($NewLine)"
Return $EncodedString
}
#Decode a Base64 string to a plain text string
Function ConvertFrom-Base64
{
[CmdletBinding(SupportsShouldProcess=$False)]
Param
(
[Parameter(Mandatory=$True, ValueFromPipeline=$True)]
[ValidateNotNullOrEmpty()]
[ValidatePattern('^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$')]
[String]$String
)
$DecodedString = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($String))
Write-Verbose -Message "$($NewLine)`"$($String)`" has been converted from the following Base64 encoded string `"$($DecodedString)`"$($NewLine)"
Return $DecodedString
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
###
@@ -0,0 +1 @@
###
+43
View File
@@ -0,0 +1,43 @@
Get the ReleaseID of Windows 10 that you intend to deploy. The script will attempt to automatically detect which version from the image path, but the folder structure must be in place to support it.
Example: 1909
Create a folder structure like the following
Example:
1909
1909\X86
1909\X64
Place the appropriate Feature On Demand cabinet files into the respective architecture of the image you are deploying.
1909\X86\Source\Place 32-Bit Feature On Demand Cabinet Files Here
1909\X64\Source\Place 64-Bit Feature On Demand Cabinet Files Here
The script will attempt to locate the cabs automatically and use them as the installation source for enabling the specified capabilities after exporting them.
Types of Features on Demand (https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/features-on-demand-v2--capabilities)
Starting with Windows 10, version 1809 and Windows Server 2019, Windows has two different types of Features on Demand:
FODs without satellite packages: FODs with all language resources packaged into the same package. These FODs are distributed as a single .cab file.
They can be added using either DISM /Add-Capability or /Add-Package.
FODs with satellite packages: Language-neutral FODs that have language and/or architecture resources in separate packages (satellites). When you install this type of FOD, only the packages that apply to the Windows image are installed, which reduces disk footprint. These FODs are distributed as a set of several .cab files, but are installed by specifying a single /capabilityname. These are new for Windows 10, version 1809.
They can only be added using DISM /Add-Capability (and not /Add-Package).
FODs with satellites require a well-formed FOD repository. This can either be the full FOD repository on the ISO, or a custom repository created with DISM /export-source. They cannot be added by pointing to a directory with a handful of FOD files hand-copied from the repository, because DISM requires additional metadata to make the right connections.
Note: As of the most recent builds of Windows 10, you can find the Feature On Demand cabinet files for the .NET Framework directly inside the ISO inside the ".\Sources\SXS" folder. Otherwise, you will have to download the Feature On Demand ISOs for your respective version of Windows 10.
Note: Sometimes Microsoft will not release a newer version of the Features On Demand iso file(s), but they will place a notice saying that you could perhaps use the most recently released version. This took place with Windows 10 1903 and 1909. You were able to use the same cabinet files for both releases.
##################################
No access to WSUS here. I didnt want to mount the ISO or copy the whole 4.6GB down to the clients to install this, so I experimented to see what the bare minimum I needed for just the Active Directory Users and Computers for Australian English was.
First, I copied only:
Microsoft-Windows-ActiveDirectory-DS-LDS-Tools-FoD-Package~31bf3856ad364e35~amd64~~.cab
Microsoft-Windows-ActiveDirectory-DS-LDS-Tools-FoD-Package~31bf3856ad364e35~amd64~en-US~.cab
When I did this, I was getting a “The source files could not be found” error. It turns out that you also need the metadata folder and the files within. I did not need FoDMetadata_Client.cab.
41 files at 6MB total.
###################################