Skip to content

Tag: Powershell

[Powershell] Create Microsoft 365 admin account on all managed tenants

I received a question from a customer asking me for a way to create MFA-enabled administrator accounts on all Microsoft 365 tenants managed through the Partner portal, without having to manually go to each tenant and creating them. To solve this, I threw together a quick script that imports a .CSV file containing the DisplayName, UserPrincipalName and Password and then goes through every managed tenant to create the accounts and enables MFA on the newly created accounts.

As an extra bonus, I’ve also provided a script that could be used to remove the accounts on all managed tenants.

Enjoy!

Account Creation

<#
.Description
	This script is used for creating (multiple) Microsoft 365 tenant administrator accounts for all tenants managed by your MSP.
	Current Version: 1.1
	
	Version History:
   ---------------- 
   v1.0: First release.
   v1.1: Fixed a bug that caused trouble when adding roles.
   
	By: Stefan van Bruggen

#>




# Connect to Microsoft 365 using your partner account credentials.

Connect-MsolService

# Get managed tenant IDs and prefixes.

Get-MsolPartnerContract -All | ForEach {
    $TenantPrefix = [string]$_.DefaultDomainName
    $TenantId = [string]$_.TenantId.Guid
      
		# Define administrator roles to be granted to the user.
      
		$Roles = "Authentication Administrator","Azure Information Protection Administrator","Company Administrator","Conditional Access Administrator","Directory Readers","Directory Synchronization Accounts","Directory Writers","Exchange Service Administrator","Helpdesk Administrator","Hybrid Identity Administrator","Intune Service Administrator","Kaizala Administrator","License Administrator","Message Center Privacy Reader","Message Center Reader","Partner Tier1 Support","Partner Tier2 Support","Password Administrator","Privileged Authentication Administrator","Privileged Role Administrator","Reports Reader","Service Support Administrator","SharePoint Service Administrator","Teams Communications Administrator","Teams Communications Support Engineer","Teams Communications Support Specialist","Teams Service Administrator","User Account Administrator"
    
		# Import users from .csv and create new user account, the .csv should have the following fields: DisplayName,UserPrincipalName,Password
      
		Import-Csv .\users.csv | ForEach {
        $newUPN = $_.UserPrincipalName + "@" + $TenantPrefix
        $newUPN = [string]$newUPN
        New-MsolUser -DisplayName $_.DisplayName -UserPrincipalName $newUPN -Password $_.Password -ForceChangePassword:$true -PasswordNeverExpires:$true -TenantId $TenantId 
        
        # Add newly created user account to previously defined administrator roles

        ForEach($role in $roles){
            Add-MsolRoleMember -TenantId $TenantId -RoleName $role -RoleMemberEmailAddress $newUPN
      }
    
    }  
      
      # Set required variables for MFA.
      
      $st = New-Object -TypeName Microsoft.Online.Administration.StrongAuthenticationRequirement
      $st.RelyingParty = "*"
      $st.State = "Enabled"
      $sta = @($st)
      
      # Enable MFA.
      
      Set-MsolUser -TenantId $TenantId -UserPrincipalName $newUPN -StrongAuthenticationRequirements $sta
}

Account Removal

<#
.Description
	This script is used for removing (multiple) Microsoft 365 tenant administrator accounts for all tenants managed by your MSP.
	Current Version: 1.0
   
	By: Stefan van Bruggen, Open ICT
		s.vanbruggen@open-ict.nl

#>


# Connect to Microsoft 365 using your partner account credentials.

Connect-MsolService

# Get managed tenant IDs and prefixes.

Get-MsolPartnerContract -All | ForEach {
    $TenantPrefix = [string]$_.DefaultDomainName
    $TenantId = [string]$_.TenantId.Guid
	
# Import list of users that need to be removed from .csv and remove the accounts, the .csv should have the following fields: UserPrincipalName
	
	Import-Csv .\delete-users.csv | ForEach {
        $UPN = $_.UserPrincipalName + "@" + $TenantPrefix
        $UPN = [string]$UPN
        Remove-MsolUser -UserPrincipalName $UPN -TenantId $TenantId -Force
    }
	
}

[Powershell] Manipulating a machine’s asset tag in the MDT database

I wrote this function for my fellow IT engineer (and brother) Robin van Bruggen who is building a script that allows his co-workers to change the OSDComputerName and AssetTag values for a specified machine without manually manipulating the MDT SQL database.

The script uses the MDTDB module created by Michael Niehaus (which can be found HERE). This module allows you to change pretty much anything you would want to change in the MDT database except for, you’ve guessed it, the asset tag.

Anyway, to keep a long story short, add this to the MDTDB.psm1 file and you’re good to go!

 

function Set-MDTComputerAssetTag {

    [CmdletBinding()]
    PARAM
    (
        [Parameter(ValueFromPipelineByPropertyName=$true, Mandatory=$true)] $id,
        [Parameter(ValueFromPipelineByPropertyName=$true)] $assetTag
    )
    
    Process
    {
        # Tell SQL which table to edit and what to look for
        $sql = "UPDATE ComputerIdentity 
        SET AssetTag = '$assetTag'
        WHERE ID = '$id'"
        Write-Verbose "About to execute command: $sql"
        $identityCmd = New-Object System.Data.SqlClient.SqlCommand($sql, $mdtSQLConnection)
        $identity = $identityCmd.ExecuteScalar()
        Write-Verbose "Added computer identity record"

        
        # Write the updated record back to the pipeline
        Get-MDTComputer -ID $id
    }
} 

 

[Powershell] Removing the NXLOG-agent without the Nagios management interface.

I made this script at the request of a colleague, who needed a quick way to remove the NXLOG-agent without having to resort to the management interface (which was unavailable).

<#
.Description
   Script for the removal of the NXLOG agent without the use of the Nagios management interface.
   
   Current Version: 1.0
   Changelog:
   ----------------
   v1.0: First version, ready for use
   
   By: Stefan van Bruggen <info@svanbruggen.nl>
   For: de Koning B.V <svbruggen@koning-ict.nl

#>

##		Define the path where the key should be located and start a recursive search
##		The script only looks for a registry key containing the right display name and publisher, and selects the LocalPackage property.

$inst = Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\* -Recurse | Get-ItemProperty | 
		Where-Object {$_.DisplayName -like 'NXLOG-CE' -and $_.Publisher -like 'nxsec.com' -and $_.Publisher -ne '' } |
		Select-Object LocalPackage -ExpandProperty LocalPackage

## 	Silent install of the package, required for the next step

Start-Process $inst -arg '/q' -Wait

##	 Silent uninstall of the agent

Start-Process "msiexec.exe" -arg "/X $inst /qn" -Wait

This script could also be modified quite easily to run on a remote computer by adding the following at the beginning of the script:

$PC='SRV-NAME-01'

Invoke-Command -ComputerName $PC -ScriptBlock {

In this case, don’t forget to add a in line 21.

[Powershell] Corrupt Userprofiles – Quick fix via Powershell

Customer X had a long ongoing problem with userprofiles getting corrupted due to their antivirus solution holding the ntuser.dat file hostage. It took a while before we found the cause of this problem so we had to think of a quick fix to keep things running.
Another problem was that the locally stored corrupted profile was getting synced to the profile server, causing trouble for users on multiple workstations.

To save time and to give the sysadmins an easy way to clean these corrupted profiles, I automated the process with this (admittedly messy) script.

I’ve also added some workarounds that start the required services used in this script, because in some cases these are not enabled (WinRM, Remote Registry, etc.)

Note: The text in the popup is in Dutch.

<#
.Description
   Script for cleaning up corrupted userprofiles.
   Gebaseerd op: 	https://technet.microsoft.com/en-us/library/ff730941.aspx
					https://msexchange.me/2014/03/11/powershell-custom-gui-input-box-for-passing-values-to-variables/

   Current Version: 1.2
   Versiebeheer:
   ----------------
   v1.0: First working version
   v1.1: Added automated starting of the required services
   v1.2: Script cleaned up
   
   Door: Stefan van Bruggen, de Koning B.V.
		 info@svanbruggen.nl

#>

function button ($title,$username,$workstation) {

## Laden van assemblys
[void][System.Reflection.Assembly]::LoadWithPartialName( “System.Windows.Forms”)
[void][System.Reflection.Assembly]::LoadWithPartialName( “Microsoft.VisualBasic”)

## Formaat van het input-venster
$form = New-Object “System.Windows.Forms.Form”;
$form.Width = 500;
$form.Height = 200;
$form.Text = $title;
$form.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen;

## Definieren van label1
$textLabel1 = New-Object “System.Windows.Forms.Label”;
$textLabel1.Left = 25;
$textLabel1.Top = 15;
$textLabel1.Text = $username;

## Definieren van label2
$textLabel2 = New-Object “System.Windows.Forms.Label”;
$textLabel2.Left = 25;
$textLabel2.Top = 50;
$textLabel2.Text = $workstation;


## Definieren van input-veld box1
$textBox1 = New-Object “System.Windows.Forms.TextBox”;
$textBox1.Left = 150;
$textBox1.Top = 10;
$textBox1.width = 200;

## Definieren van input-veld box2
$textBox2 = New-Object “System.Windows.Forms.TextBox”;
$textBox2.Left = 150;
$textBox2.Top = 50;
$textBox2.width = 200;

## Definieren van standaardwaarde input-velden
$textBox1.Text = "Gebruikersnaam";
$textBox2.Text = "Werkstation";

## Definieren van OK-knop
$button = New-Object “System.Windows.Forms.Button”;
$button.Left = 360;
$button.Top = 85;
$button.Width = 100;
$button.Text = “Ok”;

## Definieren van actie bij klikken OK
$eventHandler = [System.EventHandler]{
$textBox1.Text;
$textBox2.Text;
$form.Close();};
$button.Add_Click($eventHandler) ;

## Definieren van controls
$form.Controls.Add($button);
$form.Controls.Add($textLabel1);
$form.Controls.Add($textLabel2);
$form.Controls.Add($textBox1);
$form.Controls.Add($textBox2);
$ret = $form.ShowDialog();

## Definieren van ingevoerde waarden

return $textBox1.Text, $textBox2.Text
}

$return= button “Invoer Gebruikersgegevens” “Gebruikersnaam” “Computernaam”

## De waarden die zijn ingevoerd zijn op te vragen via
## $return[0] voor de username
## $return[1] voor het werkstation

## Define profile share
$ProfileShare = "\\SERVER01.domain.local\Profiles$\"

## Define full path to userprofile
$FullPath = $ProfileShare + $return[0] + ".v2" 

## Rename ntuser.dat on the profile server
Get-ChildItem $FullPath -recurse -force | Where {$_.name -eq "ntuser.dat"} | rename-item -newname {  $_.name  -replace "ntuser.dat","ntuser.dat_old"  }

## Get the SID for the deletion of the registry key
$usersidrequest = $return[0]
$userSID = ([System.Security.Principal.NTAccount]("ahoy.local\$usersidrequest")).Translate([System.Security.Principal.SecurityIdentifier]).Value

## Delete registry key on the werkstation and starting of Remote Registry service
$workstation = $return[1]
(Get-Service -ComputerName $workstation -Name "WinRM").Start()
(Get-Service -ComputerName $workstation -Name "RemoteRegistry").Start()

$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey(‘LocalMachine’,$workstation)

       $regKey= $reg.OpenSubKey(“SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList”,$true )
       $regKey.DeleteSubKeyTree($userSID)

## Rename locally cached userprofile to _old
$workstationunc = "\\" + $workstation + "\c$\Users\"
$workstationuser = $return[0]
$rename = $workstationuser + "_old"
Get-ChildItem $workstationunc -force | Where {$_.name -eq $workstationuser} | rename-item -newname {  $_.name  -replace $workstationuser,$rename } -ErrorAction Continue

 

 

[WSUS] Cleaning up superseded updates

Sadly, the WSUS cleanup wizard neglects to clean up updates that were approved in the past but have been superseded since.

Because these updates tend to use a lot of diskspace, I use a short Powershell script that checks all updates for superseded updates and declines them. After this, the WSUS cleanup wizard can be run again to clear up diskspace.

################################################################
#                                                              #
#  Script for cleaning up superseded updates in WSUS.          #
#                                                              #
#                                                              #
#  -Stefan van Bruggen                                         #
#                                                              #
################################################################

# Change the WSUSserver variable to your server hostname

[String]$WSUSserver = "SERVERNAAM"
[Int32]$port =8530
[Boolean]$SecureConnection = $False

# .NET assembly WSUS inladen

[void][reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration")

# Variable used for counting the total number of declined updates
$count = 0

# Connecting to server

$updateServer = [Microsoft.UpdateServices.Administration.AdminProxy]::getUpdateServer($WSUSserver,$SecureConnection,$port)

write-host "Connected to WSUS Server" -foregroundcolor "green"

$updatescope = New-Object Microsoft.UpdateServices.Administration.UpdateScope

# Get the updates and check for superseded updates
$u=$updateServer.GetUpdates($updatescope)

foreach ($u1 in $u )
{
if ($u1.IsSuperseded -eq 'True')
# Declining superseded updates
{
write-host Declined Update : $u1.Title
$u1.Decline()
$count=$count + 1
}
}
write-host Total Declined Updates: $count

exit
Stefan van Bruggen - 2019