mirror of
https://github.com/freedbygrace/Add-WindowsCapabilities.git
synced 2026-07-26 11:38:13 +00:00
Add files via upload
This commit is contained in:
@@ -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 @@
|
||||
###
|
||||
@@ -0,0 +1 @@
|
||||
###
|
||||
@@ -0,0 +1 @@
|
||||
###
|
||||
Binary file not shown.
@@ -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 @@
|
||||
###
|
||||
@@ -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 didn’t 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.
|
||||
###################################
|
||||
Reference in New Issue
Block a user