NTS.Tools.MSConfigMgr.psm1
function Install-WADK { <# .Description this function can be used to install Windows ADK and Windows ADK PE .Parameter Latest use if you want the latest version .Parameter Features a list of Windows ADK to be installed .Parameter IncludeWinPE use if you want windows adk pe installed .Parameter Outpath path where the install and log files are saved .Example # installs windows adk and windows adk pe for configmgr site server Install-WADK -Latest -IncludeWinPE -Features OptionId.DeploymentTools, OptionId.UserStateMigrationTool .NOTES requires internet connection #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateSet("latest","W11_22H2","W11_21H2","W10_2004")] [string] $Version, [Parameter(Mandatory = $true)] [ValidateSet( "OptionId.ApplicationCompatibilityToolkit", "OptionId.DeploymentTools", "OptionId.ImagingAndConfigurationDesigner", "OptionId.ICDConfigurationDesigner", "OptionId.UserStateMigrationTool", "OptionId.VolumeActivationManagementTool", "OptionId.WindowsPerformanceToolkit", "OptionId.UEVTools", "OptionId.AppmanSequencer", "OptionId.AppmanAutoSequencer", "OptionId.MediaeXperienceAnalyzer", "OptionId.MediaeXperienceAnalyzer", "OptionId.WindowsAssessmentToolkit" )] [string[]] $Features, [Parameter(Mandatory = $false)] [switch] $IncludeWinPE, [Parameter(Mandatory = $false)] [string] $Outpath = "C:\Programdata\NTS\Windows_ADK" ) switch ($Version) { {($PSItem -eq "latest") -or ($PSItem -eq "W11_22H2")} { $WADK_Download_URL = "https://download.microsoft.com/download/6/7/4/674ec7db-7c89-4f2b-8363-689055c2b430/adk/adksetup.exe" $WADK_PE_Download_URL = "https://download.microsoft.com/download/5/2/5/525dcde0-c7b8-487a-894d-0952775a78c7/adkwinpeaddons/adkwinpesetup.exe" } "W11_21H2" { $WADK_Download_URL = "https://download.microsoft.com/download/1/f/d/1fd2291e-c0e9-4ae0-beae-fbbe0fe41a5a/adk/adksetup.exe" $WADK_PE_Download_URL = "https://download.microsoft.com/download/5/5/e/55e3e34a-5708-46cd-a90d-92044c29336b/adkwinpeaddons/adkwinpesetup.exe" } "W10_2004" { $WADK_Download_URL = "https://download.microsoft.com/download/8/6/c/86c218f3-4349-4aa5-beba-d05e48bbc286/adk/adksetup.exe" $WADK_PE_Download_URL = "https://download.microsoft.com/download/3/c/2/3c2b23b2-96a0-452c-b9fd-6df72266e335/adkwinpeaddons/adkwinpesetup.exe" } Default { throw "this version '$($Version)' is not supported" } } try { if ((Test-Path -Path $Outpath) -eq $false) { New-Item -Path $Outpath -ItemType Directory -Force | Out-Null } $Outpath = (Get-Item -Path $Outpath).FullName if ($Features.count -gt 1) { $Features | ForEach-Object { [string]$Features_Selected = $Features_Selected + " " + $PSItem } } $WADK_Path = "$($Outpath)\adksetup-$($Version).exe" $WADK_LogPath = "$($Outpath)\install-adksetup-$($Version).log" $WADK_PE_Path = "$($Outpath)\adkwinpesetup-$($Version).exe" $WADK_PE_LogPath = "$($Outpath)\install-adkwinpesetup-$($Version).log" # download Write-Output "$($env:COMPUTERNAME): downloading adk setup files" Start-FileDownload -DownloadURL $WADK_Download_URL -FileOutPath $WADK_Path # install Write-Output "$($env:COMPUTERNAME): installing adk with the features $($Features_Selected)" $Process = Start-Process -FilePath $WADK_Path -ArgumentList "/quiet /norestart /features $($Features_Selected) /l $($WADK_LogPath)" -NoNewWindow -Wait -PassThru if ($Process.ExitCode -ne 0) { throw "check log at $($WADK_LogPath)" } if ($IncludeWinPE -eq $true) { # download Write-Output "$($env:COMPUTERNAME): downloading adk pe setup files" Start-FileDownload -DownloadURL $WADK_PE_Download_URL -FileOutPath $WADK_PE_Path # install Write-Output "$($env:COMPUTERNAME): installing adk pe" $Process = Start-Process -FilePath $WADK_PE_Path -ArgumentList "/quiet /norestart /features OptionId.WindowsPreinstallationEnvironment /l $($WADK_PE_LogPath)" -NoNewWindow -Wait -PassThru if ($Process.ExitCode -ne 0) { throw "check log at $($WADK_PE_LogPath)" } } Start-FolderCleanUp -FolderToRemove $Outpath } catch { throw "something went wrong $($PSItem.Exception.Message)" } } function Initialize-CM_MP_Prereq { <# .Description use this function to install configmgr management point prerequesits .Example # install configmgr management point prerequesits Initialize-CM_MP_Prereq .NOTES #> $Features = @( "NET-Framework-Core" "FileAndStorage-Services" "Storage-Services" "Web-Server" "Web-WebServer" "Web-Common-Http" "Web-Default-Doc" "Web-Dir-Browsing" "Web-Http-Errors" "Web-Static-Content" "Web-Http-Redirect" "Web-DAV-Publishing" "Web-Health" "Web-Http-Logging" "Web-Custom-Logging" "Web-Log-Libraries" "Web-ODBC-Logging" "Web-Request-Monitor" "Web-Http-Tracing" "Web-Performance" "Web-Stat-Compression" "Web-Dyn-Compression" "Web-Security" "Web-Filtering" "Web-Basic-Auth" "Web-CertProvider" "Web-Client-Auth" "Web-Digest-Auth" "Web-Cert-Auth" "Web-IP-Security" "Web-Url-Auth" "Web-Windows-Auth" "Web-App-Dev" "Web-Net-Ext" "Web-Net-Ext45" "Web-AppInit" "Web-ASP" "Web-Asp-Net" "Web-Asp-Net45" "Web-CGI" "Web-ISAPI-Ext" "Web-ISAPI-Filter" "Web-Includes" "Web-WebSockets" "Web-Ftp-Server" "Web-Ftp-Service" "Web-Ftp-Ext" "Web-Mgmt-Tools" "Web-Mgmt-Console" "Web-Mgmt-Compat" "Web-Metabase" "Web-Lgcy-Mgmt-Console" "Web-Lgcy-Scripting" "Web-WMI" "Web-Scripting-Tools" "Web-Mgmt-Service" "NET-Framework-Features" "NET-Framework-Core" "NET-Framework-45-Features" "NET-Framework-45-Core" "NET-Framework-45-ASPNET" "NET-WCF-Services45" "NET-WCF-HTTP-Activation45" "NET-WCF-MSMQ-Activation45" "NET-WCF-Pipe-Activation45" "NET-WCF-TCP-Activation45" "NET-WCF-TCP-PortSharing45" "BITS" "BITS-IIS-Ext" "BITS-Compact-Server" "MSMQ" "MSMQ-Services" "MSMQ-Server" "Windows-Defender" "RDC" "RSAT" "RSAT-Feature-Tools" "RSAT-Bits-Server" "System-DataArchiver" "PowerShellRoot" "PowerShell" "PowerShell-V2" "WAS" "WAS-Process-Model" "WAS-Config-APIs" "WoW64-Support" "XPS-Viewer" ) try { Write-Output "$($env:COMPUTERNAME): installing required features for configmgr management point" Install-WindowsFeature -Name $Features | Out-Null } catch { throw $PSItem.Exception.Message } } function Initialize-CM_DP_Prereq { <# .Description use this function to install configmgr distribution point prerequesits .Example # install configmgr distribution point prerequesits Initialize-CM_DP_Prereq .NOTES #> $Features = @( "FileAndStorage-Services" "File-Services" "FS-FileServer" "Storage-Services" "Web-Server" "Web-WebServer" "Web-Common-Http" "Web-Default-Doc" "Web-Dir-Browsing" "Web-Http-Errors" "Web-Static-Content" "Web-Http-Redirect" "Web-Health" "Web-Http-Logging" "Web-Performance" "Web-Stat-Compression" "Web-Security" "Web-Filtering" "Web-Windows-Auth" "Web-App-Dev" "Web-ISAPI-Ext" "Web-Mgmt-Tools" "Web-Mgmt-Console" "Web-Mgmt-Compat" "Web-Metabase" "Web-WMI" "Web-Scripting-Tools" "NET-Framework-45-Features" "NET-Framework-45-Core" "NET-WCF-Services45" "NET-WCF-TCP-PortSharing45" "Windows-Defender" "RDC" "System-DataArchiver" "PowerShellRoot" "PowerShell" "WoW64-Support" "XPS-Viewer" ) try { if ((Test-Path -Path "$($env:SystemDrive)\NO_SMS_ON_DRIVE.SMS") -eq $false) { Write-Output "$($env:COMPUTERNAME): creating NO_SMS_ON_DRIVE.SMS on boot volume" New-Item -Path "$($env:SystemDrive)\NO_SMS_ON_DRIVE.SMS" -ItemType File | Out-Null } else { Write-Output "$($env:COMPUTERNAME): file NO_SMS_ON_DRIVE.SMS on boot volume already exists" } Write-Output "$($env:COMPUTERNAME): installing required features for configmgr distribution point" Install-WindowsFeature -Name $Features | Out-Null } catch { throw $PSItem.Exception.Message } } function Initialize-CM_SiteServer_Prereq { <# .Description use this function to install configmgr site server prerequesits .Example # install configmgr site server prerequesits Initialize-CM_SiteServer_Prereq .NOTES #> $Features = @( "RDC" "UpdateServices-RSAT" "NET-Framework-Features" ) try { if ((Test-Path -Path "$($env:SystemDrive)\NO_SMS_ON_DRIVE.SMS") -eq $false) { Write-Output "$($env:COMPUTERNAME): creating NO_SMS_ON_DRIVE.SMS on boot volume" New-Item -Path "$($env:SystemDrive)\NO_SMS_ON_DRIVE.SMS" -ItemType File | Out-Null } else { Write-Output "$($env:COMPUTERNAME): file NO_SMS_ON_DRIVE.SMS on boot volume already exists" } Write-Output "$($env:COMPUTERNAME): installing required features for configmgr site server" Install-WindowsFeature -Name $Features -IncludeAllSubFeature | Out-Null } catch { throw $PSItem.Exception.Message } } function Initialize-CM_SUP_Prereq { <# .Description use this function to install configmgr software update point prerequesits .Example # install configmgr software update point prerequesits Initialize-CM_SUP_Prereq .NOTES #> try { Write-Output "$($env:COMPUTERNAME): installing required features for configmgr software update point" Install-WindowsFeature -Name RDC, UpdateServices-RSAT -IncludeAllSubFeature | Out-Null } catch { throw $PSItem.Exception.Message } } function Add-CM_ADContainer { <# .Description use this function to create the system management container in ad and add permissions to the local server .Example # checks if the container exits and adds permissions Add-CM_ADContainer .NOTES #> try { Import-Module -Name "ActiveDirectory" $AD_DistinguishedName = (Get-ADDomain).DistinguishedName $CM_ContainerName = "SYSTEM MANAGEMENT" Write-Output "$($env:COMPUTERNAME): adding container 'SYSTEM MANAGEMENT'" if ($null -eq (Get-ADObject -Filter 'ObjectClass -eq "container"' -SearchBase "CN=System,$($AD_DistinguishedName)" | Where-Object -Property Name -eq $CM_ContainerName)) { New-ADObject -Name $CM_ContainerName -Path "CN=System,$($AD_DistinguishedName)" -Type Container } Write-Output "$($env:COMPUTERNAME): adding permissions for the ad container" $path = "AD:\CN=$($CM_ContainerName),CN=System,$($AD_DistinguishedName)" $ADCompObject = Get-ADComputer -Identity $env:COMPUTERNAME $adRights = [DirectoryServices.ActiveDirectoryRights]::GenericAll $accessType = [Security.AccessControl.AccessControlType]::Allow $inheritance = [DirectoryServices.ActiveDirectorySecurityInheritance]::All $fullAccessACE = New-Object -TypeName DirectoryServices.ActiveDirectoryAccessRule -ArgumentList @($ADCompObject.SID, $adRights, $accessType, $inheritance) $acl = Get-Acl -Path $path $acl.AddAccessRule($fullAccessACE) Set-Acl -Path $path -AclObject $acl } catch { throw $PSItem.Exception.Message } } function Install-WSUS { <# .Description this will install the required functions for wsus and do the post install tasks .Parameter UseWID wsus with windows internal database .Parameter UseSQL wsus with mssql database .Parameter WSUSFilePath where should the file be stored .Parameter SQLInstance sql instance for wsus .Example # configures the specified network card Set-Interface -InterfaceObject $SFP10G_NICs[0] -IPAddress $CLU1_IPAddress -NetPrefix $NetPrefix -DefaultGateway $CLU_DefaultGateway -DNSAddresses $CLU_DNSAddresses -NewName "Datacenter-1" .NOTES https://smsagent.blog/2014/02/07/installing-and-configuring-wsus-with-powershell/ #> [CmdletBinding()] param ( [Parameter(ParameterSetName = 'WID')] [switch] $UseWID, [Parameter(ParameterSetName = 'SQL')] [switch] $UseSQL, [Parameter(ParameterSetName = 'WID', Mandatory = $true)] [Parameter(ParameterSetName = 'SQL', Mandatory = $true)] [string] $WSUSFilePath, [Parameter(ParameterSetName = 'SQL', Mandatory = $true)] [string] $SQLInstance # "MyServer\MyInstance" ) if ((Test-Path -Path $WSUSFilePath) -eq $false) { New-Item -Path $WSUSFilePath -ItemType Directory -Force | Out-Null } try { if ($UseWID -eq $true) { Write-Output "$($env:COMPUTERNAME): installing required features for wsus" Install-WindowsFeature UpdateServices -IncludeManagementTools -WarningAction SilentlyContinue | Out-Null Write-Output "$($env:COMPUTERNAME): doing postinstall with wid" Start-Process -FilePath "$($env:ProgramFiles)\Update Services\Tools\wsusutil.exe" -ArgumentList "postinstall CONTENT_DIR=$($WSUSFilePath)" -NoNewWindow -Wait } elseif ($UseSQL -eq $true) { Write-Output "$($env:COMPUTERNAME): installing required features for wsus" Install-WindowsFeature -Name UpdateServices-Services, UpdateServices-DB -IncludeManagementTools -WarningAction SilentlyContinue | Out-Null Write-Output "$($env:COMPUTERNAME): doing postinstall with sql instance $($SQLInstance)" Start-Process -FilePath "$($env:ProgramFiles)\Update Services\Tools\wsusutil.exe" -ArgumentList "postinstall SQL_INSTANCE_NAME=$($SQLInstance) CONTENT_DIR=$($WSUSFilePath)" -NoNewWindow -Wait } } catch { throw $PSItem.Exception.Message } } function Confirm-CM_Prerequisites { <# .Description this function will search for the configmgr install volume and run the prerequisite checks for a site server .Parameter PrereqchkFilePath path to the prereqchk.exe .Parameter CM_SiteServerFQDN fqdn of the site server .Parameter CM_SQL_Site_Instance database server with instance name, eg. <fqdn of the site server>\<instancename> .Example # this will run the checks and throw an error if something is not passed Confirm-CM_Prerequisites -CM_SiteServerFQDN $CM_SiteServerFQDN -CM_SQL_Site_Instance ($CM_SiteServerFQDN + "\" + $using:CM_SQL_Site_InstanceName) .NOTES https://learn.microsoft.com/en-us/mem/configmgr/core/servers/deploy/install/prerequisite-checker #> [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string] $PrereqchkFilePath, [Parameter(Mandatory = $true)] [string] $CM_SiteServerFQDN, [Parameter(Mandatory = $true)] [string] $CM_SQL_Site_Instance ) $CM_PrereqchkLogFilePath = "$($env:SystemDrive)\ConfigMgrPrereq.log" if ($PrereqchkFilePath -eq "") { $CM_SetupVolumes = Get-CM_Setup_Volume if ($CM_SetupVolumes.DriveLetter.count -eq 1) { $CM_Prereqchk_Filepath = "$(($CM_SetupVolumes).DriveLetter):\SMSSETUP\BIN\X64\prereqchk.exe" } else { throw "there are more than one or less than one installation media for configmgr" } } else { if (Test-Path -Path $PrereqchkFilePath) { $CM_Prereqchk_Filepath = $PrereqchkFilePath } else { throw "cannot find prereqchk.exe at $($PrereqchkFilePath)" } } if (Test-Path -Path $CM_PrereqchkLogFilePath) { Remove-Item -Path $CM_PrereqchkLogFilePath -Force | Out-Null } try { Write-Output "$($env:COMPUTERNAME): checking prerequisites for the site server role & admin console" Start-Process -FilePath $CM_Prereqchk_Filepath -ArgumentList "/NOUI /PRI /SDK $($CM_SiteServerFQDN) /SQL $CM_SQL_Site_Instance /SCP" -Wait -NoNewWindow Start-Process -FilePath $CM_Prereqchk_Filepath -ArgumentList "/NOUI /ADMINUI" -Wait -NoNewWindow } catch { throw "failed to run $($CM_Prereqchk_Filepath) - $($PSItem.Exception.Message)" } $Content = Get-Content -Path $CM_PrereqchkLogFilePath $SuccessMessage = $Content -like "*Prerequisite checking is completed.*" $FailureMessage = $Content -like "*ERROR:*" if ($null -eq $SuccessMessage[0] -and $null -ne $FailureMessage[0]) { if ($FailureMessage -like "*ERROR: Failed to connect to SQL Server 'master' db.*" -and $FailureMessage.Count -gt 2) { throw "found errors in log $($CM_PrereqchkLogFilePath):`n$($FailureMessage)" } } Write-Output "$($env:COMPUTERNAME): all prerequisites are met for configmgr installation" } function Uninstall-ConfigMgrAgent { <# .Description this function uninstalls the configmgr agent .Example # this function uninstalls the configmgr agent Uninstall-ConfigMgrAgent .NOTES https://learn.microsoft.com/en-us/mem/configmgr/core/servers/deploy/install/prerequisite-checker #> $SkipCleanup = $false try { $CCMExecServiceName = "CcmExec" $CCMSetupFilePath = "$($env:windir)\ccmsetup\ccmsetup.exe" if ($null -ne (Get-Service -Name $CCMExecServiceName -ErrorAction SilentlyContinue) -or (Test-Path -Path $CCMSetupFilePath)) { Write-Output "$($env:COMPUTERNAME): starting configmgr Agent uninstall" Start-Process -FilePath $CCMSetupFilePath -ArgumentList "/uninstall" -Wait -NoNewWindow $LogFileContent = Get-Content -Path "$($env:windir)\ccmsetup\logs\CCMSetup.log" $SuccesMessage = $LogFileContent -like "*[LOG[Uninstall succeeded.]LOG]*" } else { Write-Output "$($env:COMPUTERNAME): Service $($CCMExecServiceName) not found and no ccmsetup.exe, skipping" $SkipCleanup = $true } } catch { throw "$($env:COMPUTERNAME): error while uninstalling the agent - $($PSItem.Exception.Message)" } try { if ($SkipCleanup -eq $false) { if ($SuccesMessage.Count -gt 0) { Write-Output "$($env:COMPUTERNAME): finished configmgr Agent uninstall" Write-Output "$($env:COMPUTERNAME): doing cleanup" if (Test-Path -Path "$($env:windir)\CCM") { $Items = Get-ChildItem -Path "$($env:windir)\CCM" $Items | ForEach-Object { if ((Test-FileLock -Path $PSItem.FullName) -ne $true) { Remove-Item -Path $PSItem.FullName -Force -Recurse | Out-Null } } } if (Test-Path -Path "$($env:windir)\ccmsetup") { Remove-Item -Path "$($env:windir)\ccmsetup" -Force -Recurse | Out-Null } Write-Output "$($env:COMPUTERNAME): finished doing cleanup" } else { throw "uninstall was not successful $($PSItem.Exception.Message)" } } } catch { throw "$($env:COMPUTERNAME): error doing cleanup - $($PSItem.Exception.Message)" } } function Get-CM_Setupfiles { <# .Description downloads the eval setup of configmgr current branch .Parameter Version version of the iso .Parameter Outpath path where the setup file is stored .Example # stores the setup file to $Outpath Get-CM_Setupfiles -Version 2303 -Outpath $Outpath .NOTES downloads the configmgr current branch eval setup #> [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [ValidateSet("current", "2303")] [string] $Version = "current", [Parameter(Mandatory = $true)] [string] $Outpath ) switch ($Version) { "current" { $DownloadURL = "https://go.microsoft.com/fwlink/p/?LinkID=2195628&clcid=0x409&culture=en-us&country=us" } "2303" { $DownloadURL = "https://download.microsoft.com/download/0/0/1/001d97e2-c427-4d4b-ad30-1556ee0ff1b0/MCM_Configmgr_2303.exe?culture=en-us&country=us" } Default { throw "no version was selected" } } if ((Test-Path -Path $Outpath) -eq $false) { New-Item -Path $Outpath -ItemType Directory -Force | Out-Null } $Outpath = (Get-Item -Path $Outpath).FullName $SetupPath = "ConfigMgr-$($Version)-CB-Eval.exe" $SetupFullPath = "$($Outpath)\$($SetupPath)" try { Start-FileDownload -DownloadURL $DownloadURL -FileOutPath $SetupFullPath Write-Output "$($env:COMPUTERNAME): finished download, starting extraction to $($Outpath)" Start-Process -FilePath $SetupFullPath -ArgumentList "-s" -NoNewWindow -Wait -WorkingDirectory $Outpath $FoldersFound = Get-ChildItem -Path $Outpath | Where-Object -Property Attributes -Like "*Directory*" $FolderWithSetup = $FoldersFound | ForEach-Object { $Items = Test-Path -Path "$($PSItem.FullName)\SMSSETUP\BIN\X64\setup.exe" if($Items){ return $PSItem } } if($FolderWithSetup.count -gt 1) { throw "found more than one folder with the setup files" } Rename-Item -Path $FolderWithSetup.FullName -NewName SetupFiles Write-Output "$($env:COMPUTERNAME): finished, setup files can be found at $($Outpath)\SetupFiles" } catch { throw "error downloading eval setup - $($PSItem.Exception.Message)" } } function Get-CM_PrerequisiteFiles { <# .Description this function calls SMSSETUP\BIN\X64\Setupdl.exe from configmgr iso .Parameter SetupdlFilePath path to the Setupdl.exe .Parameter Outpath save path of the downloaded files .Example # downloads the files to $($env:SystemDrive)\Temp\ConfigMgr\SetupFiles Get-CM_PrerequisiteFiles -SetupdlFilePath "$($using:LocalConfigMgrSetupPath)\SMSSETUP\BIN\X64\setupdl.exe" -Outpath $PrerequisitePath .NOTES https://learn.microsoft.com/en-us/mem/configmgr/core/servers/deploy/install/setup-downloader #> [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string] $SetupdlFilePath, [Parameter(Mandatory = $true)] [string] $Outpath ) try { if ((Test-Path -Path $Outpath) -eq $false) { New-Item -Path $Outpath -ItemType Directory -Force | Out-Null } $Outpath = Resolve-Path $Outpath $LogFilePath = "$($env:SystemDrive)\ConfigMgrSetup.log" if ($SetupdlFilePath -eq "") { $CM_SetupVolumes = Get-CM_Setup_Volume if ($CM_SetupVolumes.DriveLetter.count -eq 1) { $CM_SetupFileDownloaderPath = "$(($CM_SetupVolumes).DriveLetter):\SMSSETUP\BIN\X64\Setupdl.exe" } else { throw "there are more than one or less than one installation media for configmgr" } } else { if (Test-Path -Path $SetupdlFilePath) { $CM_SetupFileDownloaderPath = $SetupdlFilePath } else { throw "cannot find Setupdl.exe at $($SetupdlFilePath)" } } if (Test-Path -Path $LogFilePath) { Remove-Item -Path $LogFilePath -Force | Out-Null } try { Write-Output "$($env:COMPUTERNAME): starting download of configmgr setup prerequisite files" Start-Process -FilePath $CM_SetupFileDownloaderPath -ArgumentList "/NoUI $($Outpath)" -Wait -NoNewWindow } catch { throw "failed to run $($LogFilePath) - $($PSItem.Exception.Message)" } $Content = Get-Content -Path $LogFilePath $SuccessMessage = $Content -like "*INFO: Setup downloader * FINISHED*" if ($null -eq $SuccessMessage[0]) { throw "no success message, check log $($LogFilePath)" } Write-Output "$($env:COMPUTERNAME): finished download of configmgr setup files" } catch { throw "error downloading configmgr setup files - $($PSItem.Exception.Message)" } } function Get-CM_Setup_Volume { <# .Description this function searches all volumes for the setup.exe from the configmgr iso .Example # this will return the volume where confimgr setup is Uninstall-ConfigMgrAgent .NOTES #> try { $Volumes = Get-Volume | Where-Object -FilterScript { $PSItem.DriveLetter -NE "C" -and $null -ne $PSItem.DriveLetter } $Volumes | ForEach-Object { if (Test-Path -Path "$($PSItem.DriveLetter):\SMSSETUP\BIN\X64\setup.exe") { return $PSItem } } } catch { throw $PSItem.Exception.Message } } function Initialize-CM_Schema_To_AD { <# .Description this function extends the schema using extadsch.exe for the configmgr .Parameter ExtadschFilePath path to the extadsch.exe .Example # ad schema will be prepared for configmgr Initialize-CM_Schema_To_AD .NOTES should be run on the siteserver with domain admin privileges temporarily the current user is added to the schema admin group #> [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string] $ExtadschFilePath ) try { if (((Get-ADGroupMember -Identity 'Schema Admins').Name -eq $env:USERNAME) -ne $true) { Write-Output "$($env:COMPUTERNAME): adding current user to schema admins" Add-ADGroupMember -Identity 'Schema Admins' -Members $env:USERNAME } if ($ExtadschFilePath -eq "") { $CM_SetupVolumes = Get-CM_Setup_Volume if ($CM_SetupVolumes.DriveLetter.count -eq 1) { $CM_Extadsch_Filepath = "$(($CM_SetupVolumes).DriveLetter):\SMSSETUP\BIN\X64\extadsch.exe" } else { throw "there are more than one or less than one installation media for configmgr" } } else { if (Test-Path -Path $ExtadschFilePath) { $CM_Extadsch_Filepath = $ExtadschFilePath } else { throw "cannot find extadsch.exe at $($ExtadschFilePath)" } } Write-Output "$($env:COMPUTERNAME): extending schema" Start-Process -FilePath $CM_Extadsch_Filepath -Wait -NoNewWindow $LogFilePath = "$($env:SystemDrive)\ExtADSch.log" $LogFileContent = Get-Content -Path $LogFilePath $SuccessMessage = $LogFileContent -like "*Successfully extended the Active Directory schema.*" $FailedMessages = $LogFileContent -like "*Failed to create*" if ($null -ne $SuccessMessage[0]) { Write-Output "$($env:COMPUTERNAME): finished extending schema" } else { throw "something went wrong, check the log at $($LogFilePath):`n$($FailedMessages[0])" } if (((Get-ADGroupMember -Identity 'Schema Admins').Name -eq $env:USERNAME) -ne $true) { Write-Output "$($env:COMPUTERNAME): removing current user from schema admins" Remove-ADGroupMember -Identity 'Schema Admins' -Members $env:USERNAME -Confirm:$false } } catch { throw "error extending schema - $($PSItem.Exception.Message)" } } function Install-CM_SiteServer { <# .Description this function extends the schema using extadsch.exe for the configmgr .Parameter SetupPath path to the Setup.exe .Parameter SiteName FriendlyName of the Site .Parameter SiteCode sitecode .Parameter PrerequisitePath path to prerequisite files .Parameter SQLServer fqdn of the sql server, can be the local server .Parameter SQLInstanceName name of the instance .Example # this will start the installation of a site server Install-CM_SiteServer -SiteName $CM_SiteName ` -SiteCode $CM_SiteCode ` -PrerequisitePath $PrerequisitePath ` -SQLServer $CM_Site_SQLServer ` -SQLInstanceName $CM_SQL_Site_InstanceName .NOTES https://learn.microsoft.com/en-us/mem/configmgr/core/servers/deploy/install/command-line-options-for-setup #> [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string] $SetupPath, [Parameter(Mandatory = $true)] [string] $SiteName, [Parameter(Mandatory = $true)] [string] $SiteCode, [Parameter(Mandatory = $true)] [string] $PrerequisitePath, [Parameter(Mandatory = $true)] [string] $SQLServer, [Parameter(Mandatory = $true)] [string] $SQLInstanceName ) try { $SiteServer = $($env:COMPUTERNAME + "." + $env:USERDNSDOMAIN) $SetupIniPath = "$($env:ProgramData)\NTS\ConfigMgr\SetupConfig.ini" $LogFilePath = "$($env:SystemDrive)\ConfigMgrSetup.log" $ConfigurationIni = "[Identification] Action=InstallPrimarySite CDLatest=0 [Options] ProductID=Eval SiteCode=$($SiteCode) SiteName=$($SiteName) SMSInstallDir=$($env:SystemDrive)\Program Files\Microsoft Configuration Manager SDKServer=$($env:COMPUTERNAME + "." + $env:USERDNSDOMAIN) PrerequisiteComp=1 PrerequisitePath=$($PrerequisitePath) AdminConsole=1 JoinCEIP=0 MobileDeviceLanguage=0 RoleCommunicationProtocol=HTTPorHTTPS ClientsUsePKICertificate=0 [SQLConfigOptions] SQLServerName=$($SQLServer + "\" + $SQLInstanceName) DatabaseName=$("CM_" + $SiteCode) [CloudConnectorOptions] CloudConnector=1 CloudConnectorServer=$($SiteServer) UseProxy=0 [SABranchOptions] SAActive=0 CurrentBranch=1 " New-Item -Path $SetupIniPath -ItemType File -Force | Out-Null Set-Content -Path $SetupIniPath -Value $ConfigurationIni Write-Output "$($env:COMPUTERNAME): starting configmgr site server installtion" Write-Output "$($env:COMPUTERNAME): to see the progress please view this log $($env:SystemDrive)\ConfigMgrSetup.log on the site server" Write-Output "$($env:COMPUTERNAME): this can take a while" if ($SetupPath -eq "") { $CM_SetupVolumes = Get-CM_Setup_Volume if ($CM_SetupVolumes.DriveLetter.count -eq 1) { Start-Process -FilePath "$(($CM_SetupVolumes).DriveLetter):\SMSSETUP\BIN\X64\setup.exe" -ArgumentList "/SCRIPT $($SetupIniPath)" -Wait -NoNewWindow } else { throw "there are more than one or less than one installation media for configmgr" } } else { if (Test-Path -Path $SetupPath) { Start-Process -FilePath $SetupPath -ArgumentList "/SCRIPT $($SetupIniPath)" -Wait -NoNewWindow } else { throw "cannot find the setup file at $($SetupPath)" } } $Content = Get-Content -Path $LogFilePath $SuccessMessage = $Content -like "*~===================== Completed Configuration Manager Server Setup =====================*" if ($null -eq $SuccessMessage[0]) { throw "no success message, check log $($LogFilePath)" } Write-Output "$($env:COMPUTERNAME): finished the configmgr site server installtion" } catch { throw $PSItem.Exception.Message } } function Convert-CMSiteUpdateState { <# .DESCRIPTION this function can be used to convert configmgr update status into a string .Parameter State current state of the configmgr site update .NOTES source: https://learn.microsoft.com/en-us/troubleshoot/mem/configmgr/setup-migrate-backup-recovery/understand-troubleshoot-updates-servicing#complete-list-of-state-codes .EXAMPLE Converts the state from int to string (for humans) Convert-CMSiteUpdateState -State (Get-CMSiteUpdate -Fast -Name $UpdateName).State #> # https://learn.microsoft.com/en-us/troubleshoot/mem/configmgr/setup-migrate-backup-recovery/understand-troubleshoot-updates-servicing#complete-list-of-state-codes [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string] $State ) switch ($State) { "" { $Message = "no state was provided" } "2" { $Message = "UNKNOWN" } "0x0" { $Message = "UNKNOWN" } "0x2" { $Message = "ENABLED" } "262145" { $Message = "DOWNLOAD_IN_PROGRESS" } "262146" { $Message = "DOWNLOAD_SUCCESS" } "327679" { $Message = "DOWNLOAD_FAILED" } "327681" { $Message = "APPLICABILITY_CHECKING" } "327682" { $Message = "APPLICABILITY_SUCCESS" } "393213" { $Message = "APPLICABILITY_HIDE" } "393214" { $Message = "APPLICABILITY_NA" } "393215" { $Message = "APPLICABILITY_FAILED" } "65537" { $Message = "CONTENT_REPLICATING" } "65538" { $Message = "CONTENT_REPLICATION_SUCCESS" } "131071" { $Message = "CONTENT_REPLICATION_FAILED" } "131073" { $Message = "PREREQ_IN_PROGRESS" } "131074" { $Message = "PREREQ_SUCCESS" } "131075" { $Message = "PREREQ_WARNING" } "196607" { $Message = "PREREQ_ERROR" } "196609" { $Message = "INSTALL_IN_PROGRESS" } "196610" { $Message = "INSTALL_WAITING_SERVICE_WINDOW" } "196611" { $Message = "INSTALL_WAITING_PARENT" } "196612" { $Message = "INSTALL_SUCCESS" } "196613" { $Message = "INSTALL_PENDING_REBOOT" } "262143" { $Message = "INSTALL_FAILED" } "196614" { $Message = "INSTALL_CMU_VALIDATING" } "196615" { $Message = "INSTALL_CMU_STOPPED" } "196616" { $Message = "INSTALL_CMU_INSTALLFILES" } "196617" { $Message = "INSTALL_CMU_STARTED" } "196618" { $Message = "INSTALL_CMU_SUCCESS" } "196619" { $Message = "INSTALL_WAITING_CMU" } "262142" { $Message = "INSTALL_CMU_FAILED" } "196620" { $Message = "INSTALL_INSTALLFILES" } "196621" { $Message = "INSTALL_UPGRADESITECTRLIMAGE" } "196622" { $Message = "INSTALL_CONFIGURESERVICEBROKER" } "196623" { $Message = "INSTALL_INSTALLSYSTEM" } "196624" { $Message = "INSTALL_CONSOLE" } "196625" { $Message = "INSTALL_INSTALLBASESERVICES" } "196626" { $Message = "INSTALL_UPDATE_SITES" } "196627" { $Message = "INSTALL_SSB_ACTIVATION_ON" } "196628" { $Message = "INSTALL_UPGRADEDATABASE" } "196629" { $Message = "INSTALL_UPDATEADMINCONSOLE" } Default { $Message = "could not map the state '$($State)' to state message" } } return $Message } function Get-ConfigMgrSiteUpdate { <# .DESCRIPTION fetches the information for a configmgr site update .Parameter UpdateName name of the update .NOTES .EXAMPLE returns the info for $Updatename Get-ConfigMgrSiteUpdate -Updatename $UpdateName #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $UpdateName ) try { do { try { $CMSiteUpdate = Get-CMSiteUpdate -Name $UpdateName -Fast } catch { if ($PSItem.Exception.Message -like "*The SMS Provider reported an error*") { Write-Output "$($env:COMPUTERNAME): waiting on sms provider" } else { throw $PSItem.Exception.Message } } Start-Sleep -Seconds 10 } while ( $Null -eq $CMSiteUpdate) return $CMSiteUpdate } catch { throw $PSItem.Exception.Message } } function Write-CMSiteUpdateStatus { <# .DESCRIPTION shows current status of the update .Parameter UpdateObj A single object returned from get-cmsiteupdate .Parameter Detailed turns on more detailed output of the current status .NOTES .EXAMPLE # shows the current status of the configmgr site update unitil it reaches download_success Write-CMSiteUpdateStatus -UpdateObj $UpdateObj -Detailed #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.Object] $UpdateObj, [Parameter(Mandatory = $false)] [switch] $Detailed ) try { try { $StatusMessage = Convert-CMSiteUpdateState -State $UpdateObj.State if ($Detailed) { $DetailStatusMessages = $UpdateObj | Get-CMSiteUpdateInstallStatus -Complete -Step All | Select-Object -Last 5 -Property Progress, orderid, SubStageName, Description | Format-Table -AutoSize } } catch { if ($PSItem.Exception.Message -like "*The SMS Provider reported an error.*") { Write-Output "$($env:COMPUTERNAME): waiting on sms provider" } else { throw $PSItem.Exception.Message } } Write-Output "$($env:COMPUTERNAME): $($UpdateName) - current status $($StatusMessage)" if ($Detailed -and $null -ne $DetailStatusMessages -and $DetailStatusMessages -ne "") { $DetailStatusMessages } } catch { throw "error checking status - $($PSItem.Message.Exception)" } } function Confirm-CMSiteUpdatePackageDownloaded { <# .DESCRIPTION writes the current status of configmgr site update until the update is downloaded .Parameter UpdateName name of the configmgr site update .Parameter Detailed turns on more detailed output of the current status .NOTES .EXAMPLE # shows the current status of the configmgr site update Confirm-CMSiteUpdatePackageDownloaded -UpdateName $UpdateToInstall.Name #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $UpdateName, [Parameter(Mandatory = $false)] [switch] $Detailed ) # Define update check variables $CheckCount = 0 $ServiceName = "SMS_EXECUTIVE" Write-Output "`n$($env:COMPUTERNAME): verifying update $($UpdateName) is downloaded" $StoppingStatus = "DOWNLOAD_SUCCESS", "UNKNOWN" do { if($Detailed) { Write-Output "---" } $UpdateCheckStart = Get-Date $CheckCount++ $UpdateObj = Get-ConfigMgrSiteUpdate -UpdateName $UpdateName $CMUpdateStatus = Convert-CMSiteUpdateState -State $UpdateObj.State if ($CMUpdateStatus -ne "DOWNLOAD_SUCCESS") { if ($Detailed) { Write-Output "$($env:COMPUTERNAME): times checked: $($CheckCount)" Write-CMSiteUpdateStatus -UpdateObj $UpdateObj -Detailed } else { Write-CMSiteUpdateStatus -UpdateObj $UpdateObj } if ($CheckCount -eq 40) { Write-Output "$($env:COMPUTERNAME): downloading state detected for longer than $(((Get-Date) - $UpdateCheckStart).Minutes) minutes, restarting $($ServiceName) service" Restart-Service -Name $ServiceName -Force -Verbose:$false } } else { Write-Output "$($env:COMPUTERNAME): update package $($UpdateName) is available - current status: $($CMUpdateStatus)" } if ($CheckCount -ge 150) { throw "update is not available, please check manually" } Start-Sleep -Seconds 15 } while ($StoppingStatus -notcontains $CMUpdateStatus) } function Confirm-CMSiteUpdatePrereqCheckFinished { <# .DESCRIPTION writes the current status of configmgr site update until the prereq checks are finished .Parameter UpdateName name of the configmgr site update .Parameter Detailed turns on more detailed output of the current status .NOTES .EXAMPLE # shows the current status of the configmgr site update Confirm-CMSiteUpdatePrereqCheckFinished -UpdateName $UpdateToInstall.Name #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $UpdateName, [Parameter(Mandatory = $false)] [switch] $Detailed ) # Define update check variables $CheckCount = 0 $ServiceName = "SMS_EXECUTIVE" $StoppingStatus = "PREREQ_ERROR", "PREREQ_WARNING", "INSTALL_IN_PROGRESS" Write-Output "`n$($env:COMPUTERNAME): verifying prerequisite checks for update $($UpdateName) are finished" do { if($Detailed) { Write-Output "---" } $UpdateCheckStart = Get-Date $CheckCount++ $UpdateObj = Get-ConfigMgrSiteUpdate -UpdateName $UpdateName $CMUpdateStatus = Convert-CMSiteUpdateState -State $UpdateObj.State if ($StoppingStatus -notcontains $CMUpdateStatus) { if ($Detailed) { Write-Output "$($env:COMPUTERNAME): times checked: $($CheckCount)" Write-CMSiteUpdateStatus -UpdateObj $UpdateObj -Detailed } else { Write-CMSiteUpdateStatus -UpdateObj $UpdateObj } if ($CheckCount -eq 40) { Write-Output "$($env:COMPUTERNAME): no state change detected for longer than $(((Get-Date) - $UpdateCheckStart).Minutes) minutes, restarting $($ServiceName) service" Restart-Service -Name $ServiceName -Force -Verbose:$false } } else { Write-Output "$($env:COMPUTERNAME): prereq checks for $($UpdateName) finished - current status: $($CMUpdateStatus)" } if ($CheckCount -ge 150) { throw "prereq checks took to long, check manually" } Start-Sleep -Seconds 15 } while ($StoppingStatus -notcontains $CMUpdateStatus) if ($CMUpdateStatus -eq "INSTALL_IN_PROGRESS") { Write-Output "$($env:COMPUTERNAME): installation was successfully initated for $($UpdateName), for more details, review the CMUpdate.log - current status: $(Convert-CMSiteUpdateState -State $CMUpdatePackage.State)" } elseif ($CMUpdateStatus -eq "PREREQ_ERROR") { Write-Output "$($env:COMPUTERNAME): prerequisite checks found some errors, please check manually - current status: $(Convert-CMSiteUpdateState -State $CMUpdatePackage.State)" } elseif ($CMUpdateStatus -eq "PREREQ_WARNING") { Write-Output "$($env:COMPUTERNAME): prerequisite checks found some warnings, please check manually - current status: $(Convert-CMSiteUpdateState -State $CMUpdatePackage.State)" } } function Confirm-CMSiteUpdatePackageInstallation { <# .DESCRIPTION checks the install status of a running configmgr update .Parameter UpdateName name of the configmgr site update .Parameter Detailed turns on more detailed output of the current status .NOTES .EXAMPLE # shows the current status of the configmgr site update Confirm-CMSiteUpdatePackageInstallation -UpdateName $UpdateToInstall.Name #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $UpdateName, [Parameter(Mandatory = $false)] [switch] $Detailed ) $StoppingStatus = "INSTALL_SUCCESS", "INSTALL_FAILED" Write-Output "`n$($env:COMPUTERNAME): verifying installation of update $($UpdateName) is finished" do { if($Detailed) { Write-Output "---" } $UpdateObj = Get-ConfigMgrSiteUpdate -UpdateName $UpdateName $StatusMessage = Convert-CMSiteUpdateState -State $UpdateObj.State if ($Detailed) { Write-CMSiteUpdateStatus -UpdateObj $UpdateObj -Detailed } else { Write-CMSiteUpdateStatus -UpdateObj $UpdateObj } Start-Sleep -Seconds 15 } while ($StoppingStatus -notcontains $StatusMessage) $UpdateObj = Get-ConfigMgrSiteUpdate -UpdateName $UpdateName $StatusMessage = Convert-CMSiteUpdateState -State $UpdateObj.State if ($StatusMessage -eq "INSTALL_SUCCESS") { Write-Output "$($env:COMPUTERNAME): installation of update $($UpdateName) is finished, post install steps are not finished yet." Write-Output "$($env:COMPUTERNAME): admin console updates may be required" # do { # if($Detailed) { # Write-Output "---" # } # $UpdateObj = Get-ConfigMgrSiteUpdate -UpdateName $UpdateName # $StatusMessage = Convert-CMSiteUpdateState -State $UpdateObj.State # Write-CMSiteUpdateStatus -UpdateObj $UpdateObj -Detailed # Start-Sleep -Seconds 15 # } # while ($true) # Write-Output "$($env:COMPUTERNAME): post install of configmgr site update $($UpdateName) is finished" } else { Write-Output "$($env:COMPUTERNAME): current status $($StatusMessage)" throw "something went wrong - please check the logs" } } |