The other day I needed to test if a registry key was present on an end user’s computer and make it if it didn’t exist. I performed a registry key value test with PowerShell. Since I was doing more than one, I pulled an older tool from my tool box for this one. It’s small but easy to use.
This script is only a “Try Catch” with some added inputs. We are first grabbing what we want to test in our parameters. We have two mandatory strings and a switch. The first string is for the path in which we are going to test. The second is the value we want to test. for example, if we want to see if google earth has a version number, we would give the path of HKLM:\Software\Google\Google Earth Pro and the value of Version. If we want to see that version we flip the next part which is the only switch, show value. Instead of saying true or false it will say the value.
Inside our try-catch box, we are using the Get-ItemProperty command. We select the $Value. Finally, we stop the command using the error action flag stop. This prevents the command from falling apart. Next, we throw all that information into the $Values parameter.
Aftward, we use a basic if statement. We show the value when the “showvalue” flag is set. However, if it’s not, we just send a true. Finally, the catch will tell us a false statement if the Get-ItemProperty command failed for any reason.
Conclusion
There are other ways to do this, but this was the quickest I have found. I added the show Value recently because I needed it for troubleshooting the code. Overall, this little guy is perfect to add to any script that deals with registry changes.
While reading on Reddit, I found a common thread. People need a quick way to do a Share Point File Audit. I have a PowerShell function for this in my toolbox. This tool heavily uses the Search-UnifiedAuditLog command let. The most common items I tend to audit are file modifications and deletions. This function goes through, modified, moved, renamed, downloaded, uploaded, accessed, synced, malware detection, restored from trash, locked, and finally unlocked. The Search-UnifiedAuditLog is an exchange online command at the time of this writing. Thus, you need to connect to exchange online. In this function, I am using the switch command. I will follow that structure for the breakdown. Lets first jump in with the function.
I’m glad you came to the breakdown. It means you want to know how the code works. This means you truly care about learning. Thank you. This code repeats itself a few times in different ways. So, I will call out the differences, but not the likes after the first time explaining something. The first section is our Parameters.
Parameters
We have 8 Parameters, and only one of them is mandatory. Firstly, we have the Type parameter. This mandatory validate set allows you to select from a list of commands we will be using in this function.
Deleted
Modified
Created
Moved
Renamed
Downloaded
Uploaded
Synced
Accessed
MalwareDetected
Restored
Locked
UnLocked
Afterward, we have Keep Alive. This allows us to run the command multiple times without signing back into the system. So, if you want to keep your session alive, flip that flag. Next, we have two switches. The first Switch is to pull only items edited in SharePoint itself. The next is for one drive. They are named accordingly. After that, we have a start date and an end date. These values are nullable. Basically, you don’t need them. The outfile is asking for just the name of the file. We are using the “./” to save it wherever you run the command from. Finally, we have the result size. If you want the max number of results, 5000. However, you can make this number smaller.
Begin
In our begin section, we want to test the Exchange Online Management Module. Secondly, we want to validate exchange connectivity. After that, we want to gather the date information for the start and end dates. Let’s take a look at the exchange part first.
The Get-Module command works with PowerShell 5.1. However, I have seen PowerShell flak with this command failing to pull the information. I am going to assume your PowerShell is up to date with your current version.
Afterward, we want to install the exchange online management module if we don’t detect the module. We are using the count to see how many objects are inside our module variable. If it’s 0, it’s time to install. We install it from the PSGallery.
Now, we test exchange connections. We use the Get-PSSession to review the current connections. Next, we test if the connections with the name “ExchangeOnlineInternalSession” is greater than zero. “isconnected” will produce a true or false statement.
If ($isconnected -ne "false") {
try {
Connect-ExchangeOnline
} catch {
Write-Error "Exchange Online Failed. Ending"
end
}
}
After which, we can test with. False, we try to connect. However, if there is an error, we end the script and let the user know. We are not using a credential object to authenticate because MFA should always be a thing.
#Auto Generates Start and Finish dates
if ($Null -eq $StartDate) { $StartDate = ((Get-Date).AddDays(-89)).Date }
if ($Null -eq $EndDate) { $EndDate = (Get-Date).Date }
#Tests if end date is before start date.
if ($EndDate -lt $StartDate) { $StartDate = ((Get-Date).AddDays(-89)).Date }
if ($EndDate -gt (Get-Date).Date) { $EndDate = (Get-Date).Date }
Afterward, we need to get the dates right. If the start date is null, we are going to pull 90 days back. We do this by using the standard. We do the same with the end date. If it’s null, we grab today’s date. Now to prevent errors, we check the start date and end date. The end date can’t be before the start date. This is similar to the end date. The end date can’t be greater than the current date. We use the if statement to resolve this.
Process
We begin the process by looking directly at our “Type” variable by using a switch command. The switch allows us to go through each “Type” and run the commands accordingly. Let’s look at one of the switch processes.
The data that search-unifiedauditlog produces a section called “AuditData”. This section has almost every piece of information you will need. The difference between each “Type” will be the Operations, and session id. The operations target the required logs. This creates the backbone of the Share Point File Audit. The graph below will show which operations I am using. Once you gather the operation information, we need to pull the AuditData. This data will be in JSON format. We start off by looping the records with a for each loop. Then we pull the auditdata and pipe it into convertfrom-json. Next, we create our PS Custom Object. Other than Moved, the output of the other logs contains almost the same information. See the script for the information.
Operation Filters
Deleted
FileDeleted
FileDeletedFirstStageRecycleBin
FileDeletedSecondStageRecycleBin
FileVersionsAllDeleted
FileRecycled
Modified
FileModified
FileModifiedExtended
Moved
FileMoved
Renamed
FileRenamed
Downloaded
FileDownloaded
Uploaded
FileUploaded
Synced
FileSyncDownloadedFull
FileSyncUploadedFull
Accessed
FileAccessed
FileAccessedExtended
MalwareDetected
FileMalwareDetected
Restored
FileRestored
Locked
LockRecord
UnLocked
UnlockRecord
End
Finally, it’s time for the end block. This is where we will present the data we have gathered. Firstly, we need to determine if the SharePoint or Onedrives were flipped or not.
Here we checking if both flags are not checked or if both flags are checked. Then we check if the user gave us a filename. If they did, we export our report to a csv file wherever we are executing the function from. However, if the user didn’t give us a filename, we just dump all the results.
Now, if the user selected either or, we present that information. We present those infos by using a where-object. Like before we ask if the user produced an outfile. Finally, we ask if keep alive was set. If it wasn’t we disconnect from the exchange.
Conclusion
In conclusion, auditing shouldn’t be difficult. We can quickly pull the info we need. I hope you enjoy this powerful little tools.
Today, in the field, we will discover a user’s monitors through PowerShell. This is very useful for any kind of RMM tool. I used this script with PDQ and Continuum. You can also run this script with backstage. Let’s take a look at the script for Monitor Discovery.
The first part of this script is grabbing information from the root namespace. We are looking at the wmi monitor ID information. We are using the get-ciminstance command to grab this information. the wmimonitorid produces encoded data streams. Finally, I pipe the output into a “for each object” loop. This loop is extremely important. By holding everything inside this loop, products like azure PowerShell terminal or Continuum Connectwise see everything after it as part of the initial line.
Aftward, we build out our adapter list inside the loop. I was about to find this list thanks to magnumdb. We set the interface number as the variable name and the type as that variable’s value. This way we can use a simple period call for the type later.
Next, it’s time to grab additional information. Firstly, we are grabbing the instance name. Later, you will see why.
$Instance = $_.InstanceName
Notice we are using the dollar sign followed by an underline. This means grabbing the previous piped item. In our case, the previous item is the wmimonitorid information.
Aftward, we are grabbing the basic sizes of the screen. We are doing this using the wmi object called wmimonitorbasicdisplayparam. Let me untie my tongue after that one. This cim instance produces all of the monitors’ information at once. Thus, we need to filter with a where-object. This command produces the same instance name as the previous command. That’s why we gathered that information beforehand.
Before we continue, I want you to look closely. Notice that we have inside the where-object a $_. already for the instance name. The $_ is calling the previous pipe. In this case that is the wmimonitorbasicdisplayparams. This is why we created the $Instance variable.
Certainly, we will use this same trick with our next piece of information. We will query the wmimonitorconnectionparams using the same method. However, we want only the video output technology variable. This is why we wrap the command inside a parenthesis.
Finally, we need to build the output. This is what the end user will see and interact with. This is accomplished by using the ps custom object. Lets talk about each variable as there are special conditions in the code here.
Manufacturer
We are using the system text encoding to decode the wmi monitor id data. It will be looking at the manufacturername. After decoding, we want to trim the data.
Certainly, like the previous variable, we are going to do the same thing with the name. Using the System text encoding, we get the string and trim down the userFriendlyName information.
Afterward, we work with the size. In this case we are going to do some math. The $Size variable are not encoded like the previous commands. Thus we can do some basic math. We add the max horizontal image size to the max vertical image size. Then we divide that by 2.54. Finally, we round that. This code is a little ugly.
Finally, we will use the adapter types from above. It’s as simple as using the $adaptertypes.”$connections”. See, I told you that you will see this later.
Monitor Discovery is not out of reach. PowerShell is the tool that brings this to life without costing additional money. I have used this code to grab hundreds of computer monitor information. Then I used a web scraper to grab the prices of the monitors. At the end of the day, I produced a list of computer monitors and their prices. This gave us a total number to give insurance.
At a previous company, we had to maintain windows updates without WSUS. This caused some unique complexities. Back then, all machines in question were Microsoft Surface Tablets. This means that driver updates were important. Thus, I created a one-liner to update windows. In today’s post, we will go over Windows Updates with PowerShell. Using PowerShell allows you to use tools like backstage or scripts to install updates on remote machines quickly. The first part of this post will be how to do it manually and then the final part is oneliners. PSWindowsupdate is the module we will be using.
Warnings
Today’s code has the ability to install all windows updates. This includes updates blocked by different software. Thus, reviewing the updates and being confident in what you are updating are essential to success.
The Manual Breakdown
Once you are connected to a machine that you want to do windows updates with PowerShell, start a PowerShell session. Each step from here own will help make a clear and clean method.
Execution Policy
Set-ExecutionPolicy - ExecutionPolicy Bypass
This command allows you to install modules and any other items in PowerShell. The PSWindowsUpdate will require the execution policy to be at least set to bypass. You can learn more about execution policies here. Note, you must be running PowerShell in an evaluated prompt for this code to work.
Nuget
Install-PackageProvider Nuget -Force
After setting the execution policy, we might need to update the package provider. Making a single-line script becomes a challenge because of this. With this knowledge, we want to force an installation of the newest package provider.
The next piece is to install the pswindowsupdate module. This module is the module that does our heavy lifting. Here is where we will need to use the force and confirm flags.
Import PSWindowsUpdate
Import-Module PSWindowsUpdate
Now we have the module. It is time to import the module. Importing a module does not need additional input.
Getting the Windows Update
Get-WindowsUpdate -MicrosoftUpdate
It’s time to get the updates.Here is where we grab the KB information. This is where Windows Updates with Powershell Happens. This is where you can find updates to research. It’s important to know what you are updating.
This command will install the KB that you wish without asking any questions. You will see a fancy update process bar during this time.
One-Liner Commands to Install Windows Updates With PowerShell
The following are single-line commands. These commands will install all the updates according to their purpose. The following commands have the ability to break your system. One example of this is the BitLocker update that bricked machines recently. The following command will install all the KB updates.
This command will install all updates on the machine. This includes the KB Microsoft and vendor updates. Please be aware of any dangerous updates that are in the wild. The following command will install those as well.
It’s time to Set Users’ MFA with a nice little function. This function is designed to gather information and set a user’s MFA with the method you want. It even allows you to reset and disable MFA. We are going to break this blog up a little differently. We will go through each area of the function as the breakdown. Let’s get started.
Function and Parameters
The first thing we are going to build out is the Function and the Parameters. The function command is like a wrapper for a script. This way you can add this to your PowerShell tool kit module.
Function Set-SHDUserMFA {
[CmdletBinding()]
param(
)
}
Parameters
User Principal Name
The parameters are where people select items that they want. We have a few here. The First parameter is the User Principal Name. This is like an email address. This parameter is a List of strings. Basically, you are allowed to add more than one user name to this field. To make life easier we have set a Parameter set name. The PSN is called UPN. So the end user can either type UPN or UserPrincipalName for this parameter. This parameter is Mandatory.
Function Set-SHDUserMFA {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, ParameterSetName = 'UPN', Position = 0),ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true,][string[]]$UserPrincipalName,
)
}
Status
Afterward, The status parameter allows you to set the MFA status that you want. Here we have Enabled, Disable, Enforced, and Reset. The Enabled will enable MFA. Disable of course disables. Enforced will skip the text and go straight to the Apps. Finally, the reset will reset all the MFA token flags. This is how you Set Users’ MFA. This parameter isn’t mandatory.
Finally, we have the Info switch. This switch tells the script to produce an in-depth MFA report on the user. This parameter is not mandatory. We choose not to make it mandatory because we might not care.
Chiefly, I stated that we were targeting one section at a time. Here in the begin, we will check the environment that we are working with. The MSOnline module only runs in PowerShell 5 and not PowerShell 7. Thus we need to check. Thus, we use the PS Version Table variable. This will produce the PowerShell version number. We can test against that and end the script if the user is using PowerShell 7. In the below example, we are using the write-error to let the user know what’s going on.
#Checks PS version
if ($PSVersionTable.psversion.major -ne 5) {
Write-Error "Your Powershell Version is $($PSVersionTable.psversion.major). Msonline service only works on Powershell 5. Ending."
end
}
Module Test
Aftward, we test the MSOnline module. The Get-InstalledModule tests the installation status of MSOnline. Running MSOnline command tests the status of the module load. Afterward, we either connect or error out.
#Checks to see if the module is installed if not, tells the end user to install.
if (Get-InstalledModule -name MSOnline -ErrorAction SilentlyContinue) {
#Checks to see if the msolservice if it isn't connected, we connect
if (-not (Get-MsolDomain -ErrorAction SilentlyContinue)) {
Connect-MsolService
}
} else {
Write-Error "MSOnline Services are not installed. Please install the MSOnline services with the following command `n Install-module MSOnline `n"
}
Process – Set Users’ MFA
Confirming the User
Next, we start the process section. The process is where most of the meat and potatoes are. Firstly we have the User Principal Name loop. This loop is very important. The parameter we created beforehand has multiple possible inputs. Thus, it’s best to loop through those possible inputs. We are going to call the Internal variable UPN.
foreach ($UPN in $UserPrincipalName) {
#Do Stuff Yall
}
After that, we will be building out a try/catch to test if the user exists. The try/catch will give us the ability to test the user without blowing up the script. Get-MsolUser is the command we will be using with an error action of “stop”. The catch will contain the error write-out.
#Checks if the user exists, if not, ends the upn and writes an error.
try {
$User = Get-MsolUser -UserPrincipalName $UPN -ErrorAction Stop
} catch {
Write-Error $_.Exception.Message
}
The Switch
Now that we have the user verified, it’s time to move forward. We start with the switch. The switch is a process of if statements but formatted nicely. In our case, we will be building the switch of the status parameter. If the user selected blah, then we take blah action. Here is the basic structure of a switch. It’s switch and the parameters name. Then each possible answer to that parameter. The status is a “validate set” parameter. This means all the answers are inside the parameter.
The enabled will enable the MFA of a target user. We went over some of this code in a previous blog. Firstly, we want to create a new object. This object is a Microsoft Online Administration object that focuses on Strong authentication requirements. Once we create this object we will assign properties different values. Finally, we use the Set-Msoluser to set the user accordingly.
On the other hand, We have enforced. Enforced forces the user to use an authentication app. We will follow the same path as before. Create the object, and set the properties. Then set the object to the user with the set-msoluser command. Notice the sar.state property.
Before we created an object, this time we only need a command. We are using the Reset-Msolstrongauthenticationmethodbyupn. Don’t even try saying the command without spaces. This is a beast of a command, but it works. What it does is it resets the methods that the user uses to login in. If the user selected an authentication app, this will allow the user to set another one up. Great command if a user lost their phone.
Finally, we have “disable”. Unlike before methods we will be setting the strong authentication requirements to NOTHING! ABSOLUTELY NOTHING! (UHF reference).
Finally, the Info Flag piece. Here we are going to be gathering information about the user. It’s at the end of the command because we want the information after we set the user’s MFA. The first step we take is to grab user information. We do this with the Get-Msoluser command. “But couldn’t we use the previous $user grab?” The answer is no because we want the most up-to-date. We want to reflect on the changes we have made beforehand.
#Grabs the user information
$User = Get-MsolUser -UserPrincipalName $UPN -ErrorAction Stop
Confirm Strong Authentication Requirements
Afterward, we want to test this data. The first test is, does the user have strong authentication requirements. “Strongauthenticationrequirements” property is our target. Since the property has data, we place that data into the “MFAState” parameter. However, if it doesn’t, we put “Disabled” into this variable.
#If it has a strong authentication requirement we grab the state, if the state is blank, then we return disabled
if ($User.StrongAuthenticationRequirements) {
$MFAState = $User.StrongAuthenticationRequirements.State
} else {
$MFAState = 'Disabled'
}
Find the Default
Next, we want to grab the default method type. We do this with a where-object that searches for “isdefault”. Then we expand the method types.
This will produce a string if there is a default. However, it will produce null if there is not. So we test to see if the $MethodType has data. Once we do that we start another switch. I like switches. Inside this switch we are comparing the unique names to real names. This makes life easier and allows non-admin answers they can understand. Here is a list of names and their translations.
OneWaySMS = SMS
TwoWayVoiceMobile = Phone call
PhoneAppOTP = TOTP
PhoneAppNotification = Authenticator App
However, it’s none of these, we just say, disabled.
#if the methods are not blank, we find out which one is the right method. and make since of the code. If it is blank, we say disabled.
if ($MethodType) {
switch ($MethodType) {
'OneWaySMS' { $DefaultMethodType = 'SMS' }
'TwoWayVoiceMobile' { $DefaultMethodType = 'Call' }
'PhoneAppOTP' { $DefaultMethodType = 'TOTP' }
'PhoneAppNotification' { $DefaultMethodType = 'Authenticator App' }
}
} else {
$DefaultMethodType = 'Disabled'
}
PS Custom Object
Finally, the PS custom object. This is where we return the information the user wants. The PowerShell custom object will contain the following information, the username, display name, the mfa state, and the default method type.
The last thing we do is we null out the methodtypes. Sometimes PowerShell 5 will carry over these items in a loop.
#Nulls out the method type variable because we are in a loop.
$MethodType = $null
Conclusion
When you Set Users’ MFA, you make people mad. However, life gets way more secure. This function is inside my tool belt and it helps me out almost every day. I can push hundreds of users through it quickly. I will leave you with the script itself.
The Script
Function Set-SHDUserMFA {
<#
.SYNOPSIS
Sets the MFA status of a user.
.DESCRIPTION
The script can grab the Status information of MFA of a user. It can set the users mfa to enabled, enforced, disabled, and even reset the mfa of a user.
.PARAMETER UserPrincipalName
A list of strings, the user principal names of the users you wish to preform the actions against.
.PARAMETER SetStatus
The action you wish to preform
Enabled: Enables MFA
Disable: Disables MFA
Reset: Resets MFA
Enforced: Enforces MFA
.PARAMETER Info
Grabs UPN, Displayname, The MFA state, and Default Method Type
.NOTES
Name: Set-SHDUserMFA
Author: therandomadmin
Version: 1.0
DateCreated: 8/3/2022
.LINK
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, ParameterSetName = 'UPN', Position = 0,ValueFromPipeline = $true,valueFromPipelineByPropertyName = $true,)][string[]]$UserPrincipalName,
[Parameter(Mandatory = $false)][validateset("Enabled", "Disable", "Enforced", "Reset")][string]$Status,
[Parameter()][Switch]$Info
)
BEGIN {
#Checks PS version
if ($PSVersionTable.psversion.major -ne 5) {
Write-Error "Your Powershell Version is $($PSVersionTable.psversion.major). Msonline service only works on Powershell 5. Ending."
end
}
#Checks to see if the module is installed if not, tells the end user to install.
if (Get-InstalledModule -name MSOnline -ErrorAction SilentlyContinue) {
#Checks to see if the msolservice if it isn't connected, we connect
if (-not (Get-MsolDomain -ErrorAction SilentlyContinue)) {
Connect-MsolService
}
}
else {
Write-Error "MSOnline Services are not installed. Please install the MSOnline services with the following command `n Install-module MSOnline `n"
}
}
PROCESS {
#Starts the UPN loop
foreach ($UPN in $UserPrincipalName) {
#Checks if the user exists, if not, ends the upn and writes an error.
try {
$User = Get-MsolUser -UserPrincipalName $UPN -ErrorAction Stop
}
catch {
Write-Error $_.Exception.Message
}
#Checks status
switch ($Status) {
#If enabled then we enable mfa
'Enabled' {
$sar = New-Object -TypeName Microsoft.Online.Administration.StrongAuthenticationRequirement
$sar.RelyingParty = "*"
$sar.State = "Enabled"
Set-MsolUser -UserPrincipalName $UPN -StrongAuthenticationRequirements $sar
}
#if enforced, we enforce mfa
'Enforced' {
$sar = New-Object -TypeName Microsoft.Online.Administration.StrongAuthenticationRequirement
$sar.RelyingParty = "*"
$sar.State = "Enforced"
Set-MsolUser -UserPrincipalName $UPN -StrongAuthenticationRequirements $sar
}
#if reset, resets mfa
'Reset' { Reset-MsolStrongAuthenticationMethodByUpn -UserPrincipalName $UPN }
#if disable, it removes mfa requirements
'Disable' { Set-MsolUser -UserPrincipalName $UPN -StrongAuthenticationRequirements @() }
}
#if you flip the flag for info we grab the info.
if ($Info) {
#Grabs the user information
$User = Get-MsolUser -UserPrincipalName $UPN -ErrorAction Stop
#If it has a strong authentication requirement we grab the state, if the state is blank, then we return disabled
if ($User.StrongAuthenticationRequirements) {
$MFAState = $User.StrongAuthenticationRequirements.State
}
else {
$MFAState = 'Disabled'
}
#grabs the Strong authentication methods
$MethodType = $User.StrongAuthenticationMethods | Where-Object { $_.IsDefault -eq $true } | select-object -ExpandProperty MethodType
#if the methods are not blank, we find out which one is the right method. and make since of the code. If it is blank, we say disabled.
if ($MethodType) {
switch ($MethodType) {
'OneWaySMS' { $DefaultMethodType = 'SMS' }
'TwoWayVoiceMobile' { $DefaultMethodType = 'Call' }
'PhoneAppOTP' { $DefaultMethodType = 'TOTP' }
'PhoneAppNotification' { $DefaultMethodType = 'Authenticator App' }
}
}
else {
$DefaultMethodType = 'Disabled'
}
#creates a ps object and displays all the information collected
[PSCustomObject]@{
UserPrincipalName = $User.UserPrincipalName
DisplayName = $User.DisplayName
MFAState = $MFAState
DefaultMethodType = $DefaultMethodType
}
#Nulls out the method type variable because we are in a loop.
$MethodType = $null
}
}
}
END {}
}