RSAT install via SCCM for Windows 10 1909

Wondering if anyone has gotten PSADT to deploy RSAT in Windows 10 1909? I saw this and was going to try it but wanted to use PSADT if possible…

You sure can but you’ll have to modify a few things to get PSADT to log anything.

Sure. I deploy it for a long time now with PSADT.
Just download the FoD ISO and place the required files in the Files folder.

I can post my script tomorrow once I’m back at work.

Oh man that would be awesome! Thank you!

Hi Walter,

I also deployed it with PSADT. Here is my Script:

#Specify ISO Source location

    $FoD_Source = "\\%share%\fod$\FoD_W10_v1903_and_above.ISO"   
    
    #Mount ISO
    
    
    Mount-DiskImage -ImagePath "$FoD_Source"
    
    
    $FoD_Drive = (Get-DiskImage "$FoD_Source" | Get-Volume).DriveLetter
    
    
    #Grab the available RSAT Features
    
    
    $RSAT_FoD = Get-WindowsCapability -Online | Where-Object Name -like 'RSAT.ActiveDirectory.*'
    
    
    #Install RSAT Tools
    
    
    Foreach ($RSAT_FoD_Item in $RSAT_FoD)
    
    
    {
    
    
    Add-WindowsCapability -Online -Name $RSAT_FoD_Item.name -Source ($FoD_Drive+":") 
    
    
    }
    
    
    #Dismount ISO
    
    
    Dismount-DiskImage -ImagePath "$FoD_Source"

My RSAT package is 32 MB in size compared to the couple of GB when using the whole ISO.
Seems overkill.

Just copy the required files (in your required language) to the source folder and use only those.
These are mine:


I copied the whole metadata folder without filtering as it’s only 500KB. It’s required for the install. It would fail without it.

You can obviously copy more RSAT components but these are the ones we use.

My Files folder contains two folders for the Windows 10 version for which you want to make RSAT available:
RSAT2

Script:

<#
.SYNOPSIS
	This script performs the installation or uninstallation of an application(s).
	# LICENSE #
	PowerShell App Deployment Toolkit - Provides a set of functions to perform common application deployment tasks on Windows. 
	Copyright (C) 2017 - Sean Lillis, Dan Cunningham, Muhammad Mashwani, Aman Motazedian.
	This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 
	You should have received a copy of the GNU Lesser General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
.DESCRIPTION
	The script is provided as a template to perform an install or uninstall of an application(s).
	The script either performs an "Install" deployment type or an "Uninstall" deployment type.
	The install deployment type is broken down into 3 main sections/phases: Pre-Install, Install, and Post-Install.
	The script dot-sources the AppDeployToolkitMain.ps1 script which contains the logic and functions required to install or uninstall an application.
.PARAMETER DeploymentType
	The type of deployment to perform. Default is: Install.
.PARAMETER DeployMode
	Specifies whether the installation should be run in Interactive, Silent, or NonInteractive mode. Default is: Interactive. Options: Interactive = Shows dialogs, Silent = No dialogs, NonInteractive = Very silent, i.e. no blocking apps. NonInteractive mode is automatically set if it is detected that the process is not user interactive.
.PARAMETER AllowRebootPassThru
	Allows the 3010 return code (requires restart) to be passed back to the parent process (e.g. SCCM) if detected from an installation. If 3010 is passed back to SCCM, a reboot prompt will be triggered.
.PARAMETER TerminalServerMode
	Changes to "user install mode" and back to "user execute mode" for installing/uninstalling applications for Remote Destkop Session Hosts/Citrix servers.
.PARAMETER DisableLogging
	Disables logging to file for the script. Default is: $false.
.EXAMPLE
    powershell.exe -Command "& { & '.\Deploy-Application.ps1' -DeployMode 'Silent'; Exit $LastExitCode }"
.EXAMPLE
    powershell.exe -Command "& { & '.\Deploy-Application.ps1' -AllowRebootPassThru; Exit $LastExitCode }"
.EXAMPLE
    powershell.exe -Command "& { & '.\Deploy-Application.ps1' -DeploymentType 'Uninstall'; Exit $LastExitCode }"
.EXAMPLE
    Deploy-Application.exe -DeploymentType "Install" -DeployMode "Silent"
.NOTES
	Toolkit Exit Code Ranges:
	60000 - 68999: Reserved for built-in exit codes in Deploy-Application.ps1, Deploy-Application.exe, and AppDeployToolkitMain.ps1
	69000 - 69999: Recommended for user customized exit codes in Deploy-Application.ps1
	70000 - 79999: Recommended for user customized exit codes in AppDeployToolkitExtensions.ps1
.LINK 
	http://psappdeploytoolkit.com
#>
[CmdletBinding()]
Param (
	[Parameter(Mandatory=$false)]
	[ValidateSet('Install','Uninstall')]
	[string]$DeploymentType = 'Install',
	[Parameter(Mandatory=$false)]
	[ValidateSet('Interactive','Silent','NonInteractive')]
	[string]$DeployMode = 'Interactive',
	[Parameter(Mandatory=$false)]
	[switch]$AllowRebootPassThru = $false,
	[Parameter(Mandatory=$false)]
	[switch]$TerminalServerMode = $false,
	[Parameter(Mandatory=$false)]
	[switch]$DisableLogging = $false
)

Try {
	## Set the script execution policy for this process
	Try { Set-ExecutionPolicy -ExecutionPolicy 'ByPass' -Scope 'Process' -Force -ErrorAction 'Stop' } Catch {}
	
	##*===============================================
	##* VARIABLE DECLARATION
	##*===============================================
	## Variables: Application
	[string]$appVendor = 'Microsoft'
	[string]$appName = 'RSAT Tools'
	[string]$appVersion = ''
	[string]$appArch = ''
	[string]$appLang = 'EN'
	[string]$appRevision = '01'
	[string]$appScriptVersion = '1.0.0'
	[string]$appScriptDate = '12/07/2019'
	[string]$appScriptAuthor = 'Aldin T.'
	##*===============================================
	## Variables: Install Titles (Only set here to override defaults set by the toolkit)
	[string]$installName = ''
	[string]$installTitle = ''
	
	##* Do not modify section below
	#region DoNotModify
	
	## Variables: Exit Code
	[int32]$mainExitCode = 0
	
	## Variables: Script
	[string]$deployAppScriptFriendlyName = 'Deploy Application'
	[version]$deployAppScriptVersion = [version]'3.7.0'
	[string]$deployAppScriptDate = '02/13/2018'
	[hashtable]$deployAppScriptParameters = $psBoundParameters
	
	## Variables: Environment
	If (Test-Path -LiteralPath 'variable:HostInvocation') { $InvocationInfo = $HostInvocation } Else { $InvocationInfo = $MyInvocation }
	[string]$scriptDirectory = Split-Path -Path $InvocationInfo.MyCommand.Definition -Parent
	
	## Dot source the required App Deploy Toolkit Functions
	Try {
		[string]$moduleAppDeployToolkitMain = "$scriptDirectory\AppDeployToolkit\AppDeployToolkitMain.ps1"
		If (-not (Test-Path -LiteralPath $moduleAppDeployToolkitMain -PathType 'Leaf')) { Throw "Module does not exist at the specified location [$moduleAppDeployToolkitMain]." }
		If ($DisableLogging) { . $moduleAppDeployToolkitMain -DisableLogging } Else { . $moduleAppDeployToolkitMain }
	}
	Catch {
		If ($mainExitCode -eq 0){ [int32]$mainExitCode = 60008 }
		Write-Error -Message "Module [$moduleAppDeployToolkitMain] failed to load: `n$($_.Exception.Message)`n `n$($_.InvocationInfo.PositionMessage)" -ErrorAction 'Continue'
		## Exit the script, returning the exit code to SCCM
		If (Test-Path -LiteralPath 'variable:HostInvocation') { $script:ExitCode = $mainExitCode; Exit } Else { Exit $mainExitCode }
	}
	
	#endregion
	##* Do not modify section above
	##*===============================================
	##* END VARIABLE DECLARATION
	##*===============================================
		
	If ($deploymentType -ine 'Uninstall') {
		##*===============================================
		##* PRE-INSTALLATION
		##*===============================================
		[string]$installPhase = 'Pre-Installation'

		#$OS = if($envOS.BuildNumber -eq "18362"){"1903"} elseif ($envOS.BuildNumber -eq "17763"){"1809"}

        $OS = switch ($envOS.BuildNumber)
        {
            14393 { '1607' }
            15063 { '1703' }
            16299 { '1709' }
            17134 { '1803' }
            17763 { '1809' }
            18362 { '1903' }
            18363 { '1909' }
            default {'Unknown'}
        }

        #Features on Demand for 1909 is the same for 1903
        If ($OS -eq "1909"){$OS = "1903"}


        if ($OS -lt 1809) {
            Write-Log "Windows 10 version $OS detected. No RSAT Tools found which are compatible with this version." -Source "ALTU"
            Write-Log "Only Windows 10 version 1809 and 1903 are supported." -Source "ALTU"
            Write-Log "Installation will abort..." -Source "ALTU"
            Exit-Script -ExitCode 1
        }

        $dnsTools = "Rsat.Dns.Tools~~~~0.0.1.0"
        $gpoTools = "Rsat.GroupPolicy.Management.Tools~~~~0.0.1.0"
        $adTools = "Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0"
        $dhcpTools = "Rsat.DHCP.Tools~~~~0.0.1.0"
        $FSTools = "Rsat.FileServices.Tools~~~~0.0.1.0"
		
        $dnsState = Get-WindowsCapability -Name $dnsTools -Online | Select-Object -ExpandProperty State
        $gpoState = Get-WindowsCapability -Name $gpoTools -Online | Select-Object -ExpandProperty State
        $adState = Get-WindowsCapability -Name $adTools -Online | Select-Object -ExpandProperty State
        $dhcpState = Get-WindowsCapability -Name $dhcpTools -Online | Select-Object -ExpandProperty State
        $FSState = Get-WindowsCapability -Name $FSTools -Online | Select-Object -ExpandProperty State
		
		##*===============================================
		##* INSTALLATION 
		##*===============================================
		[string]$installPhase = 'Installation'
		
        Write-Log "Microsoft RSAT Tools for Windows 10 $OS will be installed." -Source "ALTU"

        $Sources = Join-Path -Path $dirFiles -ChildPath $OS

        if ($dnsState -ne "Installed") 
        {
            Add-WindowsCapability -Name $dnsTools -Online -LimitAccess -Source $Sources
        }
        else
        {
            Write-Log "RSAT DNS Tools were already installed. State: $dnsState" -Source "ALTU"
        }

        if ($gpoState -ne "Installed") 
        {
            Add-WindowsCapability -Name $gpoTools -Online -LimitAccess -Source $Sources
        }
        else
        {
            Write-Log "RSAT GPO Tools were already installed. State: $gpoState" -Source "ALTU"
        }

        if ($adState -ne "Installed") 
        {
            Add-WindowsCapability -Name $adTools -Online -LimitAccess -Source $Sources
        }
        else
        {
            Write-Log "RSAT AD Tools were already installed. State: $adState" -Source "ALTU"
        }

        if ($dhcpState -ne "Installed") 
        {
            Add-WindowsCapability -Name $dhcpTools -Online -LimitAccess -Source $Sources
        }
        else
        {
            Write-Log "RSAT DNS Tools were already installed. State: $dhcpState" -Source "ALTU"
        }

        if ($FSState -ne "Installed") 
        {
            Add-WindowsCapability -Name $FSTools -Online -LimitAccess -Source $Sources
        }
        else
        {
            Write-Log "RSAT FileServices Tools were already installed. State: $FSState" -Source "ALTU"
        }

		
		##*===============================================
		##* POST-INSTALLATION
		##*===============================================
		[string]$installPhase = 'Post-Installation'
        
        $dnsTools = "Rsat.Dns.Tools~~~~0.0.1.0"
        $gpoTools = "Rsat.GroupPolicy.Management.Tools~~~~0.0.1.0"
        $adTools = "Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0"
        $dhcpTools = "Rsat.DHCP.Tools~~~~0.0.1.0"
        $FSTools = "Rsat.FileServices.Tools~~~~0.0.1.0"
		
        $dnsState = Get-WindowsCapability -Name $dnsTools -Online | Select-Object -ExpandProperty State
        $gpoState = Get-WindowsCapability -Name $gpoTools -Online | Select-Object -ExpandProperty State
        $adState = Get-WindowsCapability -Name $adTools -Online | Select-Object -ExpandProperty State
        $dhcpState = Get-WindowsCapability -Name $dhcpTools -Online | Select-Object -ExpandProperty State
        $FSState = Get-WindowsCapability -Name $FSTools -Online | Select-Object -ExpandProperty State

        If ($dnsState -eq "Installed" -and $gpoState -eq "Installed" -and $adState -eq "Installed" -and $dhcpState -eq "Installed" -and $FSState -eq "Installed")
        { 
            Set-RegistryKey -Key 'HKEY_LOCAL_MACHINE\SOFTWARE\YourCompany-Group\RSAT Tools' -Name 'Installed' -Value "TRUE" -Type String
            Set-RegistryKey -Key 'HKEY_LOCAL_MACHINE\SOFTWARE\YourCompany-Group\RSAT Tools' -Name 'Version' -Value "1.0" -Type String
            Write-Log "Added registry keys for detection method" -Source "ALTU"
            Write-Log "Reg key added: HKEY_LOCAL_MACHINE\SOFTWARE\YourCompany-Group\RSAT Tools" -Source "ALTU"
        }		

	}
	ElseIf ($deploymentType -ieq 'Uninstall')
	{
		##*===============================================
		##* PRE-UNINSTALLATION
		##*===============================================
		[string]$installPhase = 'Pre-Uninstallation'
		
		$OS = if(($envOS.BuildNumber -eq "18362") -or ($envOS.BuildNumber -eq "18363")){"1903"} elseif ($envOS.BuildNumber -eq "17763"){"1809"}

        $dnsTools = "Rsat.Dns.Tools~~~~0.0.1.0"
        $gpoTools = "Rsat.GroupPolicy.Management.Tools~~~~0.0.1.0"
        $adTools = "Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0"
        $dhcpTools = "Rsat.DHCP.Tools~~~~0.0.1.0"
        $FSTools = "Rsat.FileServices.Tools~~~~0.0.1.0"
		
        $dnsState = Get-WindowsCapability -Name $dnsTools -Online | Select-Object -ExpandProperty State
        $gpoState = Get-WindowsCapability -Name $gpoTools -Online | Select-Object -ExpandProperty State
        $adState = Get-WindowsCapability -Name $adTools -Online | Select-Object -ExpandProperty State
        $dhcpState = Get-WindowsCapability -Name $dhcpTools -Online | Select-Object -ExpandProperty State
        $FSState = Get-WindowsCapability -Name $FSTools -Online | Select-Object -ExpandProperty State
		
		
		##*===============================================
		##* UNINSTALLATION
		##*===============================================
		[string]$installPhase = 'Uninstallation'
		
        Write-Log "Microsoft RSAT Tools for Windows 10 $OS will be removed." -Source "ALTU"

        $Sources = Join-Path -Path $dirFiles -ChildPath $OS

        if ($dnsState -eq "Installed") 
        {
            Remove-WindowsCapability -Name $dnsTools -Online
        }
        else
        {
            Write-Log "RSAT DNS Tools were not installed. No action will be taken. State: $dnsState" -Source "ALTU"
        }

        if ($gpoState -eq "Installed") 
        {
            Remove-WindowsCapability -Name $gpoTools -Online
        }
        else
        {
            Write-Log "RSAT GPO Tools were not installed. No action will be taken. State: $gpoState" -Source "ALTU"
        }

        if ($adState -eq "Installed") 
        {
             Remove-WindowsCapability -Name $adTools -Online
        }
        else
        {
            Write-Log "RSAT AD Tools were not installed. No action will be taken. State: $adState" -Source "ALTU"
        }

        if ($dhcpState -eq "Installed") 
        {
             Remove-WindowsCapability -Name $dhcpTools -Online
        }
        else
        {
            Write-Log "RSAT DNS Tools were not installed. No action will be taken. State: $dhcpState" -Source "ALTU"
        }

        if ($FSState -eq "Installed") 
        {
             Remove-WindowsCapability -Name $FSTools -Online
        }
        else
        {
            Write-Log "RSAT FileServices Tools were not installed. No action will be taken. State: $FSState" -Source "ALTU"
        }
		
		
		##*===============================================
		##* POST-UNINSTALLATION
		##*===============================================
		[string]$installPhase = 'Post-Uninstallation'
		
        $dnsTools = "Rsat.Dns.Tools~~~~0.0.1.0"
        $gpoTools = "Rsat.GroupPolicy.Management.Tools~~~~0.0.1.0"
        $adTools = "Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0"
        $dhcpTools = "Rsat.DHCP.Tools~~~~0.0.1.0"
        $FSTools = "Rsat.FileServices.Tools~~~~0.0.1.0"
		
        $dnsState = Get-WindowsCapability -Name $dnsTools -Online | Select-Object -ExpandProperty State
        $gpoState = Get-WindowsCapability -Name $gpoTools -Online | Select-Object -ExpandProperty State
        $adState = Get-WindowsCapability -Name $adTools -Online | Select-Object -ExpandProperty State
        $dhcpState = Get-WindowsCapability -Name $dhcpTools -Online | Select-Object -ExpandProperty State
        $FSState = Get-WindowsCapability -Name $FSTools -Online | Select-Object -ExpandProperty State

        If ($dnsState -eq "NotPresent" -and $gpoState -eq "NotPresent" -and $adState -eq "NotPresent" -and $dhcpState -eq "NotPresent" -and $FSState -eq "NotPresent")
        { 
            Remove-RegistryKey -Key 'HKEY_LOCAL_MACHINE\SOFTWARE\YourCompany-Group\RSAT Tools'
            Write-Log "Removed registry key: HKEY_LOCAL_MACHINE\SOFTWARE\YourCompany-Group\RSAT Tools" -Source "ALTU"
        }	
		
	}
	
	##*===============================================
	##* END SCRIPT BODY
	##*===============================================
	
	## Call the Exit-Script function to perform final cleanup operations
	Exit-Script -ExitCode $mainExitCode
}
Catch {
	[int32]$mainExitCode = 60001
	[string]$mainErrorMessage = "$(Resolve-Error)"
	Write-Log -Message $mainErrorMessage -Severity 3 -Source $deployAppScriptFriendlyName
	Show-DialogBox -Text $mainErrorMessage -Icon 'Stop'
	Exit-Script -ExitCode $mainExitCode
}

The script creates a custom REG key that you can use as a detection method.
The REG key will only be created if all components of RSAT are detected as installed.

Thanks for this! Trying it now but erroring out… Looking for the log file and don’t see it being created… Your code has Write-Log "Microsoft RSAT Tools for Windows 10 $OS will be installed." -Source "ALTU"

What is ALTU? I don’t see it declared anywhere as a location in the script?

Thanks again!

That’s just my initials. You can put there anything you want or leave it blank.
I put my initials so it’s easier to identify my own log entries in CMTrace.

Gotcha, thanks… It’s running but no log files are being created so not sure where/why it’s failing…

Any ideas where these log files are being dropped? They are not in normal log file folder that I specified in the PSADT config.

That is weird. It should be created there. I didn’t make any specific changed there.

What I always do is: Start Powershell ISE (or VSCode) and open AppDeployToolkitMain.ps1 file and run it.
This will load all the functions and variables of the toolkit and.

When you then open Deploy-Application.ps1 you can run line by line to debug your script.

But the logfile should be created anyway. So not sure whats wrong there.

It was ID10.T error… I am getting further and a log is getting created now but it is erroring… Most likely another ID10.T…


Microsoft RSAT Tools

Error Record:

Message : The source files could not be found.

             Use the "Source" option to specify the location of the files that are required to restore the feature. For more information on 

             specifying a source location, see http://go.microsoft.com/fwlink/?LinkId=243077.

InnerException :

FullyQualifiedErrorId : Microsoft.Dism.Commands.AddWindowsCapabilityCommand

ScriptStackTrace : at , C:\Windows\ccmcache\2d\Deploy-Application.ps1: line 167

PositionMessage : At C:\Windows\ccmcache\2d\Deploy-Application.ps1:167 char:13

              +             Add-WindowsCapability -Name $dnsTools -Online -LimitAcces ...

              +             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

OK

Line 167 is Add-WindowsCapability -Name $dnsTools -Online -LimitAccess -Source $Sources…

$Sources = Join-Path -Path $dirFiles -ChildPath $OS was defined earlier…

.SYNOPSIS
Install RSAT features for Windows 10 1809 or 1903 or 1909 or 2400(19041)

.DESCRIPTION
Install RSAT features for Windows 10 1809 or 1903 or 1909 or 2400(19041). All features are installed online from Microsoft Update thus the script requires Internet access

.PARAM All
Installs all the features within RSAT. This takes several minutes, depending on your Internet connection

.PARAM Basic
Installs ADDS, DHCP, DNS, GPO, ServerManager

.PARAM ServerManager
Installs ServerManager

.PARAM Uninstall
Uninstalls all the RSAT features

Run with follow
Deploy-Application.exe Deploy-Application.ps1 -basic
or
Deploy-Application.exe Deploy-Application.ps1 -ALL
or
Deploy-Application.exe Deploy-Application.ps1 -ServerManager
or
Deploy-Application.exe Deploy-Application.ps1 -Uninstall

<#
.SYNOPSIS
This script performs the installation or uninstallation of an application(s).
# LICENSE #
PowerShell App Deployment Toolkit - Provides a set of functions to perform common application deployment tasks on Windows.
Copyright © 2017 - Sean Lillis, Dan Cunningham, Muhammad Mashwani, Aman Motazedian.
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along with this program. If not, see http://www.gnu.org/licenses/.
.DESCRIPTION
The script is provided as a template to perform an install or uninstall of an application(s).
The script either performs an “Install” deployment type or an “Uninstall” deployment type.
The install deployment type is broken down into 3 main sections/phases: Pre-Install, Install, and Post-Install.
The script dot-sources the AppDeployToolkitMain.ps1 script which contains the logic and functions required to install or uninstall an application.
.PARAMETER DeploymentType
The type of deployment to perform. Default is: Install.
.PARAMETER DeployMode
Specifies whether the installation should be run in Interactive, Silent, or NonInteractive mode. Default is: Interactive. Options: Interactive = Shows dialogs, Silent = No dialogs, NonInteractive = Very silent, i.e. no blocking apps. NonInteractive mode is automatically set if it is detected that the process is not user interactive.
.PARAMETER AllowRebootPassThru
Allows the 3010 return code (requires restart) to be passed back to the parent process (e.g. SCCM) if detected from an installation. If 3010 is passed back to SCCM, a reboot prompt will be triggered.
.PARAMETER TerminalServerMode
Changes to “user install mode” and back to “user execute mode” for installing/uninstalling applications for Remote Destkop Session Hosts/Citrix servers.
.PARAMETER DisableLogging
Disables logging to file for the script. Default is: $false.
.EXAMPLE
powershell.exe -Command “& { & ‘.\Deploy-Application.ps1’ -DeployMode ‘Silent’; Exit $LastExitCode }”
.EXAMPLE
powershell.exe -Command “& { & ‘.\Deploy-Application.ps1’ -AllowRebootPassThru; Exit $LastExitCode }”
.EXAMPLE
powershell.exe -Command “& { & ‘.\Deploy-Application.ps1’ -DeploymentType ‘Uninstall’; Exit $LastExitCode }”
.EXAMPLE
Deploy-Application.exe -DeploymentType “Install” -DeployMode “Silent”
.NOTES
Toolkit Exit Code Ranges:
60000 - 68999: Reserved for built-in exit codes in Deploy-Application.ps1, Deploy-Application.exe, and AppDeployToolkitMain.ps1
69000 - 69999: Recommended for user customized exit codes in Deploy-Application.ps1
70000 - 79999: Recommended for user customized exit codes in AppDeployToolkitExtensions.ps1
.LINK
http://psappdeploytoolkit.com
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory = $false)]
[ValidateSet(‘Install’, ‘Uninstall’, ‘Repair’)]
[string]$DeploymentType = ‘Install’,
[Parameter(Mandatory = $false)]
[ValidateSet(‘Interactive’, ‘Silent’, ‘NonInteractive’)]
[string]$DeployMode = ‘Interactive’,
[Parameter(Mandatory = $false)]
[switch]$AllowRebootPassThru = $false,
[Parameter(Mandatory = $false)]
[switch]$TerminalServerMode = $false,
[Parameter(Mandatory = $false)]
[switch]$DisableLogging = $false,
[parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[switch]$All,
[parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[switch]$Basic,
[parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[switch]$ServerManager,
[parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[switch]$Uninstall
)

Try {
## Set the script execution policy for this process
Try { Set-ExecutionPolicy -ExecutionPolicy ‘ByPass’ -Scope ‘Process’ -Force -ErrorAction ‘Stop’ } Catch { }

##*===============================================
##* VARIABLE DECLARATION
##*===============================================
## Variables: Application
[string]$appVendor = 'Microsoft'
[string]$appName = 'RSAT features...'
[string]$appVersion = ''
[string]$appArch = 'X86/X64'
[string]$appLang = 'EN'
[string]$appRevision = '01'
[string]$appScriptVersion = '1.0.0'
[string]$appScriptDate = '28/03/2020'
[string]$appScriptAuthor = 'Maarten'
##*===============================================
## Variables: Install Titles (Only set here to override defaults set by the toolkit)
[string]$installName = 'RSAT features...'
[string]$installTitle = ''

##* Do not modify section below
#region DoNotModify

## Variables: Exit Code
[int32]$mainExitCode = 0

## Variables: Script
[string]$deployAppScriptFriendlyName = 'Deploy Application'
[version]$deployAppScriptVersion = [version]'3.8.1'
[string]$deployAppScriptDate = '28/03/2020'
[hashtable]$deployAppScriptParameters = $psBoundParameters

## Variables: Environment
If (Test-Path -LiteralPath 'variable:HostInvocation') { $InvocationInfo = $HostInvocation } Else { $InvocationInfo = $MyInvocation }
[string]$scriptDirectory = Split-Path -Path $InvocationInfo.MyCommand.Definition -Parent

## Dot source the required App Deploy Toolkit Functions
Try {
	[string]$moduleAppDeployToolkitMain = "$scriptDirectory\AppDeployToolkit\AppDeployToolkitMain.ps1"
	If (-not (Test-Path -LiteralPath $moduleAppDeployToolkitMain -PathType 'Leaf')) { Throw "Module does not exist at the specified location [$moduleAppDeployToolkitMain]." }
	If ($DisableLogging) { . $moduleAppDeployToolkitMain -DisableLogging } Else { . $moduleAppDeployToolkitMain }
} Catch {
	If ($mainExitCode -eq 0) { [int32]$mainExitCode = 60008 }
	Write-Error -Message "Module [$moduleAppDeployToolkitMain] failed to load: `n$($_.Exception.Message)`n `n$($_.InvocationInfo.PositionMessage)" -ErrorAction 'Continue'
	## Exit the script, returning the exit code to SCCM
	If (Test-Path -LiteralPath 'variable:HostInvocation') { $script:ExitCode = $mainExitCode; Exit } Else { Exit $mainExitCode }
}

#endregion
##* Do not modify section above
# Create Pending Reboot function for registry

# Windows 10 1809 build
$1809Build = "17763"
# Windows 10 1903 build
$1903Build = "18362"
# Windows 10 1909 build
$1909Build = "18363"
# Windows 10 2400 build
$19041Build = "19041"
function Test-PendingRebootRegistry {
	$PendingReboot = Get-PendingReboot
	if ($PendingReboot.IsSystemRebootPending) {
		$true
	} else {
		$false
	}
}

##*===============================================
##* END VARIABLE DECLARATION
##*===============================================

If ($deploymentType -ine 'Uninstall' -and $deploymentType -ine 'Repair') {
	##*===============================================
	##* PRE-INSTALLATION
	##*===============================================
	[string]$installPhase = 'Pre-Installation'
	
	# Check for administrative rights
	if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
		Show-DialogBox -Title 'Installed Error' -Text '"The script requires elevation"' -Icon 'Stop'
		[int32]$mainExitCode = 3010
		Exit-Script -ExitCode $mainExitCode
	}
	
	# Get running Windows build
	$WindowsBuild = (Get-CimInstance -Class Win32_OperatingSystem).BuildNumber
	# Get information about local WSUS server
	$WUServer = (Get-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name WUServer -ErrorAction Ignore).WUServer
	#$DualScan = (Get-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name DisableDualScan -ErrorAction Ignore).DisableDualScan
	$TestPendingRebootRegistry = Test-PendingRebootRegistry
	
	##*===============================================
	##* INSTALLATION
	##*===============================================
	[string]$installPhase = 'Installation'
	 
	if (($WindowsBuild -eq $1809Build) -OR ($WindowsBuild -eq $1903Build) -OR ($WindowsBuild -eq $1909Build) -OR ($WindowsBuild -eq $19041Build)) {
		Show-DialogBox -Text "Running correct Windows 10 build number for installing RSAT with Features on Demand. Build number is: $WindowsBuild" -Icon 'Information'
		Write-Log -Message "Running correct Windows 10 build number for installing RSAT with Features on Demand. Build number is: $WindowsBuild" -Severity 1 -Source $deployAppScriptFriendlyName
		Write-Log -Message "***********************************************************" -Severity 1 -Source $deployAppScriptFriendlyName
		
		if ($WUServer -ne $null) {
			Write-Log -Message "A local WSUS server was found configured by group policy: $WUServer" -Severity 1 -Source $deployAppScriptFriendlyName
			Write-Log -Message "You might need to configure additional setting by GPO if things are not working" -Severity 1 -Source $deployAppScriptFriendlyName
			Write-Log -Message "The GPO of interest is following: Specify settings for optional component installation and component repair" -Severity 1 -Source $deployAppScriptFriendlyName
			Write-Log -Message "Check ON: Download repair content and optional features directly from Windows Update..." -Severity 1 -Source $deployAppScriptFriendlyName
			Show-DialogBox -Text "See log for Information" -Icon 'Information'
			Write-Log -Message "***********************************************************" -Severity 1 -Source $deployAppScriptFriendlyName
				
		}
		
		if ($TestPendingRebootRegistry -eq "True") {
			Show-DialogBox -Text "Reboots are pending. The script will continue, but RSAT might not install successfully" -Icon 'Information'
			Write-Log -Message "Reboots are pending. The script will continue, but RSAT might not install successfully" -Severity 1 -Source $deployAppScriptFriendlyName
			Write-Log -Message "***********************************************************" -Severity 1 -Source $deployAppScriptFriendlyName
		}
		
		if ($PSBoundParameters["All"]) {
			Show-DialogBox -Text "Script is running with -All parameter. Installing all available RSAT features" -Icon 'Information'
			Write-Log -Message "Script is running with -All parameter. Installing all available RSAT features" -Severity 1 -Source $deployAppScriptFriendlyName
			$Install = Get-WindowsCapability -Online | Where-Object { $_.Name -like "Rsat*" -AND $_.State -eq "NotPresent" }
			if ($Install -ne $null) {
				foreach ($Item in $Install) {
					$RsatItem = $Item.Name
					Show-DialogBox -Text "Adding $RsatItem to Windows" -Icon 'Information'
					Write-Log -Message "Adding $RsatItem to Windows" -Severity 1 -Source $deployAppScriptFriendlyName
					try {
						Add-WindowsCapability -Online -Name $RsatItem
					} catch [System.Exception]
					{
						Show-DialogBox -Text "Failed to add $RsatItem to Windows" -Icon 'Exclamation'
						Write-Log -Message "Failed to add $RsatItem to Windows" -Severity 1 -Source $deployAppScriptFriendlyName
						Write-Log -Message $_.Exception.Message -Severity 3 -Source $deployAppScriptFriendlyName
						Show-DialogBox -Text $_.Exception.Message -Icon 'Exclamation'
					}
				}
			} else {
				Show-DialogBox -Text "All RSAT features seems to be installed already" -Icon 'Information'
				Write-Log -Message "All RSAT features seems to be installed already" -Severity 1 -Source $deployAppScriptFriendlyName
			}
		}
		
		if ($PSBoundParameters["Basic"]) {
			Show-DialogBox -Text "Script is running with -Basic parameter. Installing basic RSAT features" -Icon 'Information'
			Write-Log -Message "Script is running with -Basic parameter. Installing basic RSAT features" -Severity 1 -Source $deployAppScriptFriendlyName
			# Querying for what I see as the basic features of RSAT. Modify this if you think something is missing. :-)
			$Install = Get-WindowsCapability -Online | Where-Object { $_.Name -like "Rsat.ActiveDirectory*" -OR $_.Name -like "Rsat.DHCP.Tools*" -OR $_.Name -like "Rsat.Dns.Tools*" -OR $_.Name -like "Rsat.GroupPolicy*" -OR $_.Name -like "Rsat.ServerManager*" -AND $_.State -eq "NotPresent" }
			if ($Install -ne $null) {
				foreach ($Item in $Install) {
					$RsatItem = $Item.Name
					Show-DialogBox -Text "Adding $RsatItem to Windows" -Icon 'Information'
					Write-Log -Message "Adding $RsatItem to Windows" -Severity 1 -Source $deployAppScriptFriendlyName
					try {
						Add-WindowsCapability -Online -Name $RsatItem
					} catch [System.Exception]
					{
						Show-DialogBox -Text "Failed to add $RsatItem to Windows" -Icon 'Exclamation'
						Write-Log -Message "Failed to add $RsatItem to Windows" -Severity 1 -Source $deployAppScriptFriendlyName
						Show-DialogBox -Text $_.Exception.Message -Icon 'Exclamation'
						Write-Log -Message $_.Exception.Message -Severity 3 -Source $deployAppScriptFriendlyName
						
					}
				}
			} else {
				Show-DialogBox -Text "The basic features of RSAT seems to be installed already" -Icon 'Information'
				Write-Log -Message "The basic features of RSAT seems to be installed already" -Severity 1 -Source $deployAppScriptFriendlyName
			}
		}
		
		if ($PSBoundParameters["ServerManager"]) {
			Show-DialogBox -Text "Script is running with -ServerManager parameter. Installing Server Manager RSAT feature" -Icon 'Information'
			Write-Log -Message "Script is running with -ServerManager parameter. Installing Server Manager RSAT feature" -Severity 1 -Source $deployAppScriptFriendlyName
			$Install = Get-WindowsCapability -Online | Where-Object { $_.Name -like "Rsat.ServerManager*" -AND $_.State -eq "NotPresent" }
			if ($Install -ne $null) {
				$RsatItem = $Install.Name
				Show-DialogBox -Text "Adding $RsatItem to Windows" -Icon 'Information'
				Write-Log -Message "Adding $RsatItem to Windows" -Severity 1 -Source $deployAppScriptFriendlyName
				try {
					Add-WindowsCapability -Online -Name $RsatItem
				} catch [System.Exception]
				{
					Show-DialogBox -Text "Failed to add $RsatItem to Windows" -Icon 'Exclamation'
					Write-Log -Message "Failed to add $RsatItem to Windows" -Severity 1 -Source $deployAppScriptFriendlyName
					Show-DialogBox -Text $_.Exception.Message -Icon 'Exclamation'
					Write-Log -Message $_.Exception.Message -Severity 3 -Source $deployAppScriptFriendlyName
				}
			} else {
				Show-DialogBox -Text "$RsatItem seems to be installed already" -Icon 'Information'
				Write-Log -Message "$RsatItem seems to be installed already" -Severity 1 -Source $deployAppScriptFriendlyName
			}
		}
		
		if ($PSBoundParameters["Uninstall"]) {
			Show-DialogBox -Text "Script is running with -Uninstall parameter. Uninstalling all RSAT features" -Icon 'Information'
			Write-Log -Message "Script is running with -Uninstall parameter. Uninstalling all RSAT features" -Severity 1 -Source $deployAppScriptFriendlyName
			# Querying for installed RSAT features first time
			$Installed = Get-WindowsCapability -Online | Where-Object { $_.Name -like "Rsat*" -AND $_.State -eq "Installed" -AND $_.Name -notlike "Rsat.ServerManager*" -AND $_.Name -notlike "Rsat.GroupPolicy*" -AND $_.Name -notlike "Rsat.ActiveDirectory*" }
			if ($Installed -ne $null) {
				Write-Log -Message "Uninstalling the first round of RSAT features" -Severity 1 -Source $deployAppScriptFriendlyName
				# Uninstalling first round of RSAT features - some features seems to be locked until others are uninstalled first
				foreach ($Item in $Installed) {
					$RsatItem = $Item.Name
					Show-DialogBox -Text "Uninstalling $RsatItem from Windows" -Icon 'Information'
					Write-Log -Message "Uninstalling $RsatItem from Windows" -Severity 1 -Source $deployAppScriptFriendlyName
					try {
						Remove-WindowsCapability -Name $RsatItem -Online
					} catch [System.Exception]
					{
						Show-DialogBox -Text "Failed to uninstall $RsatItem from Windows" -Icon 'Exclamation'
						Write-Log -Message "Failed to uninstall $RsatItem from Windows" -Severity 3 -Source $deployAppScriptFriendlyName
						Show-DialogBox -Text $_.Exception.Message -Icon 'Exclamation'
						Write-Log -Message $_.Exception.Message -Severity 3 -Source $deployAppScriptFriendlyName
					}
				}
			}
			# Querying for installed RSAT features second time
			$Installed = Get-WindowsCapability -Online | Where-Object { $_.Name -like "Rsat*" -AND $_.State -eq "Installed" }
			if ($Installed -ne $null) {
				Show-DialogBox -Text "Uninstalling the second round of RSAT features" -Icon 'Information'
				Write-Log -Message "Uninstalling the second round of RSAT features" -Severity 1 -Source $deployAppScriptFriendlyName
				# Uninstalling second round of RSAT features
				foreach ($Item in $Installed) {
					$RsatItem = $Item.Name
					Show-DialogBox -Text "Uninstalling $RsatItem from Windows" -Icon 'Information'
					Write-Log -Message "Uninstalling $RsatItem from Windows" -Severity 1 -Source $deployAppScriptFriendlyName
					try {
						Remove-WindowsCapability -Name $RsatItem -Online
					} catch [System.Exception]
					{
						Show-DialogBox -Text "Failed to remove $RsatItem from Windows" -Icon 'Exclamation'
						Write-Log -Message "Failed to remove $RsatItem from Windows" -Severity 3 -Source $deployAppScriptFriendlyName
						Show-DialogBox -Text $_.Exception.Message -Icon 'Exclamation'
						Write-Log -Message $_.Exception.Message -Severity 3 -Source $deployAppScriptFriendlyName
					}
				}
			} else {
				Show-DialogBox -Text "All RSAT features seems to be uninstalled already" -Icon 'Information'
				Write-Log -Message "All RSAT features seems to be uninstalled already" -Severity 1 -Source $deployAppScriptFriendlyName
			}
		}
	} else {
		Show-DialogBox -Text "Not running correct Windows 10 build: $WindowsBuild" -Icon 'Exclamation'
		Write-Log -Message "Not running correct Windows 10 build: $WindowsBuild" -Severity 2 -Source $deployAppScriptFriendlyName
		
	}
	
	##*===============================================
	##* POST-INSTALLATION
	##*===============================================
	[string]$installPhase = 'Post-Installation'
	
	## <Perform Post-Installation tasks here>
	
	## Display a message at the end of the install
	
} ElseIf ($deploymentType -ieq 'Uninstall') {
	##*===============================================
	##* PRE-UNINSTALLATION
	##*===============================================
	[string]$installPhase = 'Pre-Uninstallation'
	
	
	
	##*===============================================
	##* UNINSTALLATION
	##*===============================================
	[string]$installPhase = 'Uninstallation'
	
	
	
	##*===============================================
	##* POST-UNINSTALLATION
	##*===============================================
	[string]$installPhase = 'Post-Uninstallation'
	
	## <Perform Post-Uninstallation tasks here>
	
	
} ElseIf ($deploymentType -ieq 'Repair') {
	##*===============================================
	##* PRE-REPAIR
	##*===============================================
	
	
	##*===============================================
	##* REPAIR
	##*===============================================
	[string]$installPhase = 'Repair'
	
	
	
	##*===============================================
	##* POST-REPAIR
	##*===============================================
	[string]$installPhase = 'Post-Repair'
	
	## <Perform Post-Repair tasks here>
	
	
}
##*===============================================
##* END SCRIPT BODY
##*===============================================

## Call the Exit-Script function to perform final cleanup operations
Exit-Script -ExitCode $mainExitCode

} Catch {
[int32]$mainExitCode = 60001
[string]mainErrorMessage = "(Resolve-Error)"
Write-Log -Message $mainErrorMessage -Severity 3 -Source $deployAppScriptFriendlyName
Show-DialogBox -Text $mainErrorMessage -Icon ‘Stop’
Exit-Script -ExitCode $mainExitCode
}

@maarten, thank you for the reply… Does yours download the files required from the internet? I don’t see a source parameter…

That inside windows 10

Yea, that won’t work for me because I run the application as system which doesn’t have internet access…

I need a working script that points to offline source files…

@aldin has a working one but something in my code must be different as it’s kicking an error for me…

It really works. Is your source folder variable created correctly? If you run that line, does it define the variable like you would expect and can you browse to that folder?
If yes, perhaps your source folder doesn’t contain the correct files?

So here’s what I’ve done… I opened AppDeployToolkitMain.ps1 and Deploy-Application.ps1 in Visual Studio Code, as an admin…

I “Run and Debug Powershell” on the APpDeployToolkitMain.ps1… I then run the “Deploy-Application.ps1”…

I have to machines… One on 1803 where I’m not expecting this to work and one on 1909 which it should…

The 1909 machine is the one that kicks the error about the source files… The 1803 won’t run because it’s working, it’s not compatible…

So with Visual Studio code still open I select the “$Sources = Join-Path -Path $dirFiles -ChildPath $OS” code and run it… I then go down to the terminal in VSCode and type in “$Sources”

On the machine that is 1803, it gives me:

C:\Temp\RSAT\Files\1803

On the 1909 machine I do the exact same thing and it returns nothing… Stumped…

What do you get when you run $OS?
You can also manually construct the path as a string to test it:

$Sources = “$dirFiles\folderwith1809sources”

$dirFiles is the Files folder of the toolkit. No matter from where it runs, it’ll always resolve to the Files folder.

Finally got it… Thanks so much for the help… I just used $Sources = “$dirFiles" instead of adding the $OS as I hadn’t realized that was looking for a subfolder with the files…

Thanks again!

So something weird… On a few machines I am getting source not found when it tries the $adTools…

So I literally added every:

Microsoft-Windows-ActiveDirectory-DS-LDS-Tools-FoD-Package * amd64 *

and

Microsoft-Windows-AdminTools-FOD-Package~ * amd64 *

files and it still returns the error that the source files can’t be found… Works on some, not on others… Two test machines are both 1909…

Any ideas?? Thanks! I know this error has nothing to do with your script itself, that’s working… It’s my source files that I didn’t know if you had any ideas.

Thanks