Windows Defender Install – ERROR_SXS_ASSEMBLY_MISSING

During a recent Windows Defender deployment we ran in to an issue with the onboarding script where the Windows Defender feature would fail to install with Enable-WindowsOptionalFeature : The referenced assembly could not be found.

When checking the the CBS log under C:\Windows\Logs\CBS\CBS.log we found that the issue was related to a missing package for a previously installed update.

CBS Failed to pin deployment while resolving Update: Package_8092_for_KB5005043~31bf3856ad364e35~amd64~~10.0.1.3.5005043-16635_neutral from file: (null) [HRESULT = 0x80073701 – ERROR_SXS_ASSEMBLY_MISSING]

After a bit of troubleshooting there where two fixes to this issues, if the update that is missing is available, we can download the update MSU / CAB file from the Microsoft update catalog using the KB ID. I have covered this in a previous blog post so wont go over that fix on this post use the below link to view that post.

In some of our case the update file was not available to download any more, in this case we need to modify the registry to set the package values that are corrupted to be ignored.

First we need to get the list of packages that are showing in the CBS logs as corrupted. The below script will go through the CBS log and get the packages and format the results.

$cbsLog = "c:\windows\logs\cbs\cbs.log"
$results = @()

Write-Host "Checking CBS logs for SXS Assembly Errors" -ForegroundColor Green
$checkingfailure = Get-Content $cbsLog | Select-String "ERROR_SXS_ASSEMBLY_MISSING"

$cbsresults = Get-Content $cbsLog  | Select-String "Resolving Package:"

      

if ($cbsresults) {

    foreach ($cbsresult in $cbsresults) {

        $packageresult = ($cbsresult | Out-String).Split(":").trim().Split(',')| Select-String "Package_"

        $results += $packageresult
    }
}

$results | Select-Object -Unique

Now that we have the list we need to set the local administrators group as the owner of the component registry key in order to be able to update the effect packages current state so they wont be checked.

Open regedit and go to

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing

Select Administrators as the owner and replace owner on subcontainters and objects.

Last step is to set the local administrators with full control of the registry key.

Now that we have the permissions set we can modify the registry values. This can be either scripted or manually done.

The current value needs to be change to 0 to set.

Set each of the corrupted package to 0

Then revert the permission back so Administrators have read access and trusted installer (NT Service\TrustedInstaller) is the owner of the components registry key and subkeys.

Now when we try and run the onboarding script again the Windows Defender feature enables without issue.

PowerShell Beginner’s Guide – Creating Functions

In this post we will be going through creating a functions in PowerShell.

PowerShell functions are reusable code created to perform specific tasks. Functions can accept inputs (parameters), process data, and produce outputs (return data).

PowerShell functions make code readability and efficiency better and allows easier automate of tasks.

Functions can be either saved in script file (.ps1), then called as part of the script or saved as modules (.psm1) and these can then be imported using the import-module command or added to a PowerShell profile so they load each time PowerShell is launched.

First we will go through creating a basic function, this can be made up of existing PowerShell commands, standard command line or we can add .Net Name spaces and classes.

Functions

To create a new function we type function and set a name. Then a script block to contain the code that will be run. When naming a functions it can be called anything but it is a good idea to keep the same Verb-Noun that is used by other PowerShell commands.

The below example will create a new command called Check-Logfiles and when its run it will use get-childitem to look in the specific logs folder

function Check-Logfiles {

Get-ChildItem C:\temp\Logs

}
Function

This can be useful if you have a repeatable tasks as you can create a new command and instead of having to add all the parameter each time to an existing command, you can just set in the function and run each time to check logs, services or any other specific checks.

Parameters

Most PowerShell commands, such as cmdlets, functions and scripts, rely on parameters to allow users to select options or provide inputs.

Parameters can be set to either required or not required by adding mandatory.

When setting parameters we need to use a type, the most common type I use is string but there are many additional like Boolean or date.

String: This can either be hardcoded text or a variable. This can then be passed to the command in the function.

Bool: Set the script to use $True, $False, 1 or 0.

Parameters can be set to either be required or not required by adding mandatory.

The below is the update Check-Logfiles function above and replaces the path with a $path variable and a confirm parameter to run the script.

 function Check-Logfiles {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)] 
        [string]$path,
        [bool]$Confrim
    )

    Get-ChildItem $path
    }
Function with parameters

Next we an add in the Boolean to put in true or false requirement to run the script.

function Check-Logfiles {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)] 
        [string]$path,

        [bool]$Confrim
    )

    if($Confrim -eq "True"){
    Get-ChildItem $path
    }

    else {
        Write-Warning "Confirm not set to true"
    }
    
    }
Function with Boolean

Parameter Validation

We an add validation to parameters to set what values will be accept by the parameter. To use validation we will add ValidateSet to the parameter.

For the below example we will only be accepting two paths in the Check-Logfiles function. If a users set a path outside other than the set paths the function will fail immediately and output the reason to the PowerShell console.

function Check-Logfiles {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)] 
        [ValidateSet('C:\temp\Logs','C:\temp\Logs2')]
        [string]$path
    )

    Get-ChildItem $path
    
    }

The above are just a few examples of the different ways to use a function. Functions are great way to create reusable code / commands and give structure to your scripts. Functions should be kept a small as possible and set to do only a single task this will allow easier troubleshooting.

Multiple functions can be combined in a single script to complete more complex tasks.

PowerShell Beginner’s Guide – PowerShell Remoting

In this we will be going through PowerShell remoting (PSRemoting).

PowerShell remoting is a feature that enables administrators to manage multiple computers remotely using PowerShell commands. It allows us to execute commands, run scripts, and manage configurations on one or more remote computers from a single console.

With PSremoting, we can run administrative tasks, automate processes, and manage Windows and Linux without needing to access each machine through RDP or direct console.

PowerShell Remoting User Rights

The two local groups allows users can connect to a machines remotely using PSRemoting by default:

  • Administrators
  • Remote Management Users

If users don’t require admin rights on the machine and but they will need to remote on, they should just be added to remote management users. It always best to setup least privileges for security.

Enabling PowerShell Remoting

First we need to enable PSRemoting, if it is not enabled or the WinRM ports are blocked between the two machines. We will receive and error like the below.

PS Remoting Error

To enable PSRemoting we need to run

Enable-PSRemoting
Enable PowerShell Remoting

Once enabled we can now connect using

Enter-PSSession ComputerName
PowerShell Remote Connection

Now that we are connect we can run commands as if we had the PowerShell console open directly on the remote computer.

Running Remote Command

To exit and return back to local PowerShell console, we just need to run

Exit-PSSession
Exiting PowerShell Session

Invoke-Command

Using Enter-PSSession is good for running commands against one machine but if we want to run against multiple machines we can use Invoke-command.

When running we specify the command that will be run in the sriptblock inbetween the two curly brackets {}.

Invoke-Command -ComputerName computer1,computer2 -ScriptBlock { Command}

In the below example I am running against three machines and getting the computersystem WIM class.

PowerShell Remoting Multiple Machines

PowerShell Sessions

Another method to run multiple commands against machines is to create a PowerShell session and then re-using this initial connection.

In the below example I am connect to the three machines, checking the printer spooler service and stopping the service in the third command.

PowerShell Sessions

This has been a quick few examples of setting up and using PSRemoting. Using PSRemoting makes administration a lot easier on remote machines. .

Some security teams don’t want remoting enabled (it is enabled by default on Windows Servers OS since 2012) as they see its as a security risk, while there are risks the benefits out way the risks and there are many ways to harden and reduce security risks associated with PowerShell rather than disabling PSRemoting.

PowerShell Beginner’s Guide – Error Handling Methods

In this post we will be going through different error handling methods available in PowerShell and examples on using these.

Error handling is very usefully to learn, it allow us to write scripts that can export / write errors to a log or completed certain task if a part of the script fails.

There are also two types of errors Non-Terminating and Terminating, Non-Terminating error can cause issue when using certain error handling methods, we will cover this more when we get to the try-catch section.

PowerShell has a few different ways to handle errors gracefully:

  1. Error: When using $error, we can return error messages that are stored in the default variable.
  2. Error Action Preferences: PowerShell provides different error action preferences like Stop, Continue SilentlyContinue…, allowing how to control the behavior when an error occurs.
  3. ErrorVariable: Creates a new variable to store error messages, this can then be used later to output or logging.
  4. Try-Catch: The try block run the code specified and in the event of an error the catch block catches and handles those errors. This allows for us to control how errors are managed within the script.
  5. Finally: PowerShell includes a finally block which executes regardless of whether an error occurs or not. This is useful for performing cleanup tasks or finalizing operations, like send a log file or email .

By using these features, PowerShell allows handle errors more effectively.

$Error variable

$Error is the default variable that all errors are written to, we can call this in side a script and use the index operator [0], zero returns the latest error.

I would generally not use this method but it can be usefully for simpler scripts that we just want to export the error while the script runs.

Below are some examples of using $error.

$Error[0]
Error Variable

We can also call specific properties to export more specific error propertied. We can view the properties by using Get-Member

$Error | Get-Member
Error variable additional properties

To chose a error propriety we put dot and exception.

Error variable exception

ErrorAction Parameter

ErrorAction Allows us to specify the action PowerShell should take when an error occurs. This is done using -ErrrorAction parameter.

I would generally only use this when I need to set an error to terminate and I am using error variable and don’t want to write the error to the PowerShell console.

Below example shows setting error action to continue silently.

Get-ChildItem -Path 'C:\Folder_Does_Not_Exist' -ErrorAction SilentlyContinue
Error action

ErrorVariable

ErrorVariable is used to captures error information into a custom variable, this can then be used to output to a log file or out to the PowerShell console.

This is a good method to create multiple errors objects and write a log file that outputs errors while the script runs.

Below example we are silencing the error and then adding it to a new a variable.

Error variable
Get-ChildItem -Path 'C:\Folder_Does_Not_Exist' -ErrorVariable MyErrors

$MyErrors

Below example shows exporting to a txt file.

Error variable out to file
Get-ChildItem -Path 'C:\Folder_Does_Not_Exist' -ErrorVariable MyErrors -ErrorAction SilentlyContinue

$MyErrors.Exception | Out-File C:\temp\Logs\Error_log.txt -Append

Try-Catch-Finally

The last method we will cover is try catch method, this method allows us to catch and handle errors gracefully within a script and then specify code to run in case of an error.

Try catch is a more complex error handling method as it allow for additional code or actions to be perform that can be as simple as out putting error or running additional command that could be use to fix or alert for issues while the script is running.

When using try catch the error need to be a terminating error or the catch wont work. Get-ChildItem isn’t a terminating error, due to this we will be using -ErrorAction stop to make it terminate.

Below examples show the difference when using a terminating and non-terminating error with try catch.

Try Catch terminating vs non
try {
    Get-ChildItem -Path 'C:\Folder_Does_Not_Exist'  -ErrorAction Stop
} 
catch {
    Write-Host "An error has occorred" $Error[0].ErrorDetails"
} 

We can add finally to the try catch to run a command at the end whether or not there is an error.

This can be useful if we want the script to do something like copy the log file or send a email or notification the script has completed.

Below example will remove the log file after the script has completed.

Try Catch Finally
try {
    Get-ChildItem -Path 'C:\Folder_Does_Not_Exist' -ErrorAction Stop
} 
catch {
    Write-Host "An error has occorred" $Error[0].ErrorDetails
} Finally { 
    Remove-Item C:\temp\Logs\Error_log.txt -Verbose
}

The above methods shows the different way to handle errors in PowerShell, being able to use error handling in scripts is very useful skill to learn and helps with more complex scripts.

PowerShell Beginner’s Guide – Using IF Else Statements

In this post we will be going through using if else statements.

If else statement in PowerShell evaluates a condition and executes code if the condition is true. If the condition is false, alternative code is executed.

If else can also be nested to create more complex scripts that handle multiple conditions and execute specific code for each condition.

If else follows the below format

if (condition) {
    # code to execute if condition is true
} 
else {
    # code to execute if condition is false
}

If Statement

We will first go through using a single if statement, when using a if the part between the two () is the conditions which needs to be checked.

When using an if without an operator (eq, like, gt…..), the if will check that the variable is not empty.

The below shows the difference between the if statement when there is a value and no value.

When using an operator we can specify if the value matches then run the code.

$con = "True"
if($con -eq "True"){
    Write-Host "Con is true" -ForegroundColor Green
}

If Else

Next we will go through using if and else. Adding else is a way to executed code if none of the conditions are meet.

$con = "True"
if($con -eq "True"){
    Write-Host "Con is true" -ForegroundColor Green
}
else
{
    Write-Host "Con is false" -ForegroundColor Red
}

We can add an elseif to add another condition, there is no limit to how many can be added but it can be a bit messy if there are two many if elseif’s. Also the first condition that is meet is will stop the rest from being tested.

$con = "True"
if($con -eq "True"){
    Write-Host "Con is true" -ForegroundColor Green
}
elseif ($con -eq "False") {
    Write-Host "Con is false" -ForegroundColor Red
}
else
{
    Write-Host "Con does not meet condition" -ForegroundColor Yellow
}

If with additional operator

We can test multiple conditions by using different operator in the if statement. In the below we are using the -and operator to check that condition one is true and condition two is greater than 3

We can also use -or operator which allows for us to specify two expressions and returns true if either one of them is true.

$con1 = "True"
$con2 = "5"
if ($con1 -eq "True" -or $con2 -gt "6"){Write-Host "Condition are meet" -ForegroundColor Green}

If Null

We can also check for Null values in If statements, to check if a variable or result is null we use the $null variable.

The $null variable must be on the left side of the statement as if it not PowerShell may not

Using if statements in your PowerShell scripts creates a lot of possibilities for creating dynamic and responsive automation. They can be a bit confusing at the start but with a bit of practice and experimentation, if statements become powerful tools in your scripting.

PowerShell Beginner’s Guide – Looping through objects

In this post we will be going through the different types of loops in PowerShell.

There are a few different types of loops and we will go through each and showing examples on how to use them. Loops are very useful and will be required in most scripts.

The first we will go through is a ForEach-Object loop, this is one of the main loops I use and can be used a few different ways.

Foreach Objects

First way is to get a list of objects and then pipe to our foreach-object to go through each.

Below example will get all services, loop through the results and return each name.

Get-Service | ForEach-Object { $_.Name}
Foreach-Object

Another method for using foreach-object is to use a variable with a set of object or a command that will retrieve objects and loop through each object in the variable.

There are two short hand commands for foreach-object, % and foreach.

In the below I am getting all folders under the directory and adding them to a $folders variable and looping through each.

$folders = Get-ChildItem C:\temp\ -Directory
foreach ($folder in $folders){$folder | Select-Object Name,CreationTime}

We can also use the foreach method on the variable to loop through the objects.

I mostly use foreach in my scripts as I find it the easiest to read when I have to go back over my own scripts.

While Loops

The other type of loops is a while loop.

Do while basically mean that while a condition is true the script will then keep looping.

This can be useful if we want to run a script for a certain period of time or while a process is running.

Below will loop through till the number in the variable $i is 10 then it will run the second write host.

$i = 1;
Do {
    Write-host "is $i less then 10" -ForegroundColor Red
    $i++;
}
While ($i -lt 10)
Write-Host "is 10 or greater" -ForegroundColor Green

Next we will go through do until, the different between do while and do until is that while uses true and until uses false condition.

This could be used if we wanted to check if a certain number of files or in an install script we could wait for certain process to finish before completing the next part.

Below example I am using a do until on a folder to check how many logs are there and using a do while to create the logs.

Check logs count.

Do {

    $logs = Get-ChildItem -Path C:\temp\Logs
    Start-Sleep 5
    Write-Host "Less than 10 logs found" -ForegroundColor Green
}
Until ($logs.count -gt "10" )
Write-Host "More than 10 logs found" -ForegroundColor Red

Create logs files.

$i = 1;
Do {
    New-Item "C:\temp\Logs\Log$i.log"
    $i++;
}
While ($i -lt 10)

This has been overview of some of the different types of loops and how to use them. There are pros and cons to using each and the right one to use will really depend on what the script is being used for.

Using Group Managed Services Account with Scheduled Tasks

In this post we will be going through the steps required to create and use group managed services account (gMSA) with a scheduled task.

gMSA are a managed domain account that provides automatic password management. These accounts provide a single identity to use on multiple servers.

By using a gMSA account, we can configure services / scheduled tasks with the gMSA principal and Active Directory handles the password management.

gMSAs where introduced since Windows Server 2012.

There are pre-requests to use gMSA that most domain should already meet, this is AD Schema of 52 (2012) and at least one 2012 DC.

Once we meet the minimum scheme the second part is that we have to create the Key Distribution Services Root Key.

This involves running a command in PowerShell, we can the below command to confirm that a kds root key doesn’t exist already.

Get-KdsRootKey
Get KDS Root Key

To create the KDS run

Add-KdsRootKey –EffectiveImmediately
Adding root key

Now when we check KDS again we can see the root key.

Get KDS Root Key

Now that we have the KDS root key we can create the gMSA

We can add the host either individually or using a security group, we will be using a group in this post as it will be easier to mange and just need to add any additional servers to the group to allow access.

I have create a group called tskgmsa_access to use and added some server computer accounts.

The below command is used to create the gMSA account (The DNS is required by the command but not needed for running scheduled task so you can use whatever name as it doesn’t need to be resolvable)

New-ADServiceAccount -name gMSAName -DNSHostName DNSName -PrincipalsAllowedToRetrieveManagedPassword "Group or Hosts"
Create gMSA Account

The accounts are create under the Managed Services Accounts OU.

Managed Accounts OU

We can add the account to a security group to give more rights, this could be used if the account needs admin right to do a specific tasks.

The Microsoft documentation says that you need to install RSAT tools and run Install-ADServiceAccount but I didn’t have to do this for scheduled task to run.

To allow the account to run a script we need to add the account to the logon as a batch job under user rights assignment. This can either be done using group policy or using secpol.msc.

I used secpol as i only have two servers to configure.

To test we will create a scheduled task that will out put text to a log file.

Below is the script file that will be run.

Test Script

To use the gMSA account we have to create the task using PowerShell as the GUI can’t find the account.

Below is the command I used.

$arg = "-ExecutionPolicy Bypass -NoProfile -File C:\scriptpath\script.ps1"

$ta = New-ScheduledTaskAction -Execute C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe  -Argument $arg

$tt = New-ScheduledTaskTrigger -At "Time to Run"

$ap = New-ScheduledTaskPrincipal -UserID Domain\gMSA_AccountName -LogonType Password

Register-ScheduledTask gMSA_Test_Task –Action $ta –Trigger $tt –Principal $ap

If you get incorrect users or password and only recently added the computer account to the security group, the server will need a reboot to pickup the membership.

Scheduled Task Error
Create Scheduled Task
Scheduled Task

Next we need to run the task and confirm data is written to the log. From the task events we can see the account used is gmsa_tsksch$.

Scheduled Task Event

We can also see the authentication on the domain controller.

Logon Event

When we check the logs folders we can see the text files are created.

Test Log

This has been a overview of creating and using a Group Managed Service Account for running scheduled tasks.

PowerShell Beginner’s Guide – Filtering Objects

In this post we will be going over filtering in PowerShell and different methods available.

Filtering should be done as close to the source command as possible, this will speed up the time it takes for the command to complete and return the data.

Depending on the command used there can be a filter parameters available, if there is no filtering we would use where-object.

Where-Object can be used to filter objects based on any property that is returned from the command.

If we need help with example or the right syntax to run the command we can use

Get-Help Where-Object

Get-Help Where-Object -Examples
Where-object help

Where-object can be used with lots of filtering parameters to return objects like Contains, eg (equal to), gt (greater than), lt (less than), like…….

https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/where-object?view=powershell-7.4#parameters

Once we have the syntax worked out we can start to filter the data to returned. In the below command we will return any file that has .pdf extension and just select the name to make the results easier to read.

Get-ChildItem -Path 'C:\Program Files\' -Recurse -File | Where-Object {$_.Name -like "*.pdf"} | Select-Object Name
where-object results

Get-ChildItem has a filter parameter, this allow us to test and show the speed difference when we can.

Below is the time when running the command using where-object.

Where-object command time

When we use the -filter with Get-ChildItem we can see that the time to run went from 6 second to 2 seconds.

Get-ChildItem Filter command time

This was only a small subset of data but if we where running against thousand or hundred of thousand of files this can add up and save minutes to hours of time for data to returned.

Windows Server 2022 RDS HA Broker: Privilege not held

I have been building and setting up some new Windows Server 2022 RDS farms recently and ran in to an issue when adding a second RDS connect broker after configuring High Availability.

The server would install the broker role and then fail to configure. This would return the below error.

The list of joined nodes could not be retrieved on servername. Privilege not held.

RDS Broker Error

I was also getting EventID 32814 and 4119 in the Microsoft-Windows-Rdms-UI/Admin event logs.

I enabled the debug logging also but this only returned the same error as where showing in the event logs.

https://learn.microsoft.com/en-us/troubleshoot/windows-server/remote/log-files-to-troubleshoot-rds-issues

To fix the issue I had to logon to the server I was trying to add as a secondary broker and remove the below registry value.

Make sure to backup the registry key before deleting any values.

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tssdis\Parameters\DBConnString

Tssdis Service Registry Key

After deleting, I had to remove the RDS broker roles from the server that failed to install and reboot.

Then I tried the install again and this time it completed without issue.

This seems to be a bug in Windows Server 2022.

PowerShell Beginner’s Guide – Variables, Arrays and Hash Tables

In this post we will be going through the use of variables, arrays and hash table.

Variables are object that are created temporarily in the PowerShell console or created as part of script that are removed once the console is closed. There are system variables that are part of PowerShell but we will just be covering user variables in this post.

Variables are $ sign with a name.

We can then call the variable by using the name

Variable

There are two type of quotation marks single will only display the text between the quotes. We can use these for variable that have text data.

Single Quotes Variable

With double quotation marks we can passthrough text but also passthrough a variable. If we try to add a variable without double quotes it will just write the variable name and not the text in the variable.

Double Quotes Variable

There are some instance where we need to create a subexpression using $() around the variable to pass through specific data.

In the below I want to return just the first folder. The first command return all the folder names.

The second command is using a subexpression, we are then able to return the first folder name.

Subexpression results

An array is pretty similar to a variable the only really difference is that it has multiple objects broken up by using comma’s.

Text Array

We can also use @() to create any array if there is text other than number we have to use quotes.

Number Array

We can also create a blank array, I use these when doing reports and want to output data from a script.

Blank Array

We will go more in depth on using the blank array in a future post but I mostly use them with hash tables.

Next we will go through using a hash table, these are like array but there is key / value pair.

Below we have a table where we created a name, surname and role heading and set a value for each.

Hash table

We can also use array or variables in hash tables

Hash table with array

This is where hash tables can come in hand for doing reports as we can output the results of multiple commands using a blank array and a hash table, then export the results to a CSV, text or html file.

We will cover both these in later post.

In this post we covered create variables, types of quotes and there uses, array’s and hash tables with some examples of each.