Bulk add and remove Office 365 Licences

I recently had a to move around a few thousand EMS licences to enable MFA for Office 365 and Azure, I decided to do two quick scripts to remove and add back the licences to the required users. I thought I would do a quick post on how I moved the licences.

As always any scripts should be tested on a subset of users before running on larger groups to test that they work as expected.

For this script we need the Office365 PowerShell module installed.

To check if the module is installed run

Get-Module -ListAvailable MSOnlineBulkAdd

First step is to get the AccountSKU to do this run

Import-Module MSonline and then Connect-MsolServiceBulkAdd2

Get-MsolAccountSku | Select-Object AccountSkuIdBulkAdd3

To make things easier and more repeatable in case I need to remove or add other licence I am using Out-GridView -PassThru to select the CSV file and also the licence SKU.

First Out-GridView is for the Csv file with UserPrincipalName (UPN)BulkAdd4

The second is to select the SKU to be removedBulkAdd5

Once the two items are selected the script will then runBulkAdd6

The full remove license script is below. The only part that needs to be updated is the $csv variable to point to the correct folder where the csv files will be kept.
## Bulk Remove licenses ##
## Select Csv file
$csv = Get-ChildItem -Path C:\temp\Office365Licence\Remove\ -File | Out-GridView -PassThru

## Import Csv
$users = Import-Csv $csv.FullName

## Select Account SKU to be removed
$accountSKU  = Get-MsolAccountSku | Select-Object AccountSkuId | Out-GridView -PassThru

## Loop through each user in the Csv
foreach($user in $users){
Write-Host "Removing $($accountSKU.AccountSkuId) licence from $($user.UserPrincipalName)" -ForegroundColor Yellow

## Remove licence
Set-MsolUserLicense -UserPrincipalName $user.UserPrincipalName -RemoveLicenses $accountSKU.AccountSkuId

The add script is the same only I added a check to confirm if the user requires the licence. The only part that needs to be updated is the $csv variable to point to the correct folder where the csv files will be kept.

Just a note on this I was applying the licence to existing users who where already setup with a usage location so if this is not set the script will error out. 

## Bulk Add licences ##
## Select Csv file
$csv = Get-ChildItem -Path C:\temp\Office365Licence\Add\ -File | Out-GridView -PassThru

## Import Csv
$users = Import-Csv $csv.FullName

## Select Account SKU to be removed
$accountSKU  = Get-MsolAccountSku | Select-Object AccountSkuId | Out-GridView -PassThru

## Loop through each user in the Csv
foreach ($user in $users) {

## Check if Licence is already applied
$check = Get-MsolUser -UserPrincipalName $user.UserPrincipalName | Select-Object UserPrincipalName,Licenses
Write-Warning "checking for $($accountsku.AccountSkuId) on $($user.UserPrincipalName)"
if ($check.Licenses.AccountSkuId -notcontains $accountsku.AccountSkuId){

## Add licence
Write-Warning "Adding $($accountSKU.AccountSkuId) licence to $($users.UserPrincipalName)"
Set-MsolUserLicense -UserPrincipalName $user.UserPrincipalName -AddLicenses $accountSKU.AccountSkuId

## Licence already applied
Write-Host "$($user.UserPrincipalName) has $($accountsku.AccountSkuId) licence assigned" -ForegroundColor Green


Using VMware PowerCli Part 1

Since VMware 6.0 I have started to use VMware PowerCli module to automate task and checks that I do daily or for large task that would take a long time to do manually. I am going to go through installing PowerCli and some of the useful command and script that can be use to check VMware.

To install PowerCli there are some pre-req’s

OS Type
  • Windows Server 2012 R2
  • Windows Server 2008 R2 Service Pack 1
  • Windows 10
  • Windows 8.1
  • Windows 7 Service Pack 1
  • Windows PowerShell 3.0, 4.0, 5.0, or 5.1
  • .NET Framework 4.5, 4.5.x, 4.6, or 4.6.x

I would recommend installing the latest version of PowerShell which is currently 5.1

To check current version of PowerShell run $PSVersionTablePcli

To install the latest version install the latest Windows Management Framework 5.1 link to download page is below.


Step 1 is to install PowerCli

The old PowerCli was a PowerShell snap in and required downloading an exe to install, the new version is module based and can be installed directly from the PowerShell console. To install run the below command.

Install-Module VMware.PowerCliPcli1

If you need to update the module to a new release run

Update-Module VMWare.PowerCliPcli2

If a path is not specified the default location that the module files will be placed in is

C:\Program Files\WindowsPowerShell\Modules

Once the install has finished to verify that PowerCli is installed run the below commandPcli3

Step 2 is to connect to vCenter

To connect to vCenter open an elevated PowerShell console and import the VMware Module

Import-Module VMware.PowerCliPcli4

Connect-VIServer lab-VC vCenterServerPcli5

Once connected we can now start to run command against vCenter.

To get host information run



To find all VM’s that have snapshots over a certain date. I want to get all snapshots older than 1 day to change this just edit the $date variable.

$date = (Get-Date).AddDays(-1)
$Snapshot = get-vm | get-snapshot
$Snapshot | where {$_.Created -lt $date}Pcli7

To check datastores you can run



To get additional info you can do some math’s and use arrays to get % free space of the datastores. The below will get all datastores that have less than 25% free space.

Get-Datastore | select Name,@{N=”UsedSpaceGB”;E={[math]::Round(($_.CapacityGB),2)}},
@{N=”%Free”;E={[math]::Round(($_.FreeSpaceGB)/($_.CapacityGB)*100,2)}} |
where %Free -lt “25”

Add secondary IP to Network Adapter Windows

A web developer was adding IP’s to some existing web server and once these were added we started having routing issue on the servers where the sites would not respond and the server could not talk back to the production network from the DMZ.

I checked the server and I was getting inconsistent results as I could ping a device sometimes but not others so it looked like a network issue.

I ran the below command to check what traffic was being sent and I saw that there where multiple IP’s being used as local address for traffic.

Get-NetTCPConnection -LocalAddress 192.168.0.*

I decided to check the skipasSource on the interface as this was the cause of a similar issue that happened before.

This command’s will return all IP’s the interface name and whether the IP is set to skipassource the new IP should have this set to true.


Netsh int ipv4 show ipaddresses level=verbose


Get-NetIPAddress | Select-Object IPAddress, InterfaceAlias, SkipAsSource

When I checked the new IP was set to false which meant both the orignal and new IP where being used. To set the IP to false I either need to remove the IP and set it using cmd or PowerShell  to set skip as source to true or Set to true using PowerShell.

If you don’t want to remove and re-add the address use the below command

Set-NetIPAddress -IPAddress "" -InterfaceAlias "LAN" -SkipAsSource $false

second option is to remove the IP from the network settings and  re-add the IP using command line below is using CMD and PowerShell.


Netsh int ipv4 add address “Interface Name” “IP Address” “Netmask” skipassource=True                                                                                                                                 

Below is a full example:

Netsh int ipv4 add address "LAN" skipassource=true

PowerShell:                                                                                                                                     New-NetIPAddress –IPAddress “IPAddress”  –InterfaceAlias “Interface Name” –SkipAsSource $True

Below is a full example:

New-NetIPAddress –IPAddress -PrefixLength 24 -InterfaceAlias "LAN" –SkipAsSource $True

Once skip as source was set to true there was no connection issues.

Deploy Multiple VM’s using PowerCLI and VMware Template

I wanted to create a few different VM’s so I can test a VMware daily report script. This can be done using deploy VM from template using the vSphere web client but this can take a long time.

I decided to write a quick PowerShell script to deploy the VM’s use PowerCLI and a CSV file. First we need to get the template that are available in vCenter server.

Connect to to vCenters server using PowerCLI and then run Get-Template to list all templates and then copy the name of the template. This will be added to the csv file.

This image has an empty alt attribute; its file name is image-82.png

Since I am going to be using a csv file to create the VM’s this needs to be created with headings that will be called in the script.

Below are the heading I used in the csv, these can either be used as is or the script can be updated using different headings.


Below is the completed csv file I will be using.

Below is the script I used you just need to update the $vms variable with the path that has the csv file.

$vms = Import-Csv -Path D:\Scripts\VMware\Deploy_VMs\Deploy_VMs.csv
foreach ($vm in $vms) {
Write-Warning "Creating $($vm.Name) in $($vm.cluster)"
New-VM -Name $vm.Name -Datastore $vm.Datastore -Template $vm.Template  -ResourcePool $vm.Cluster

To confirm the VM’s are being created we can check vCenter Server for running tasks.

The deployment can take awhile depending on the size of the template.

I then used the below command to get the list of VM’s and their datastores just to confirm all VM’s have been created and are in the correct datastore.

Get-VM -Name LAB-Linux* | Select Name,@{N="Datastore";E={(Get-Datastore -Id $_.DatastoreIdList)}}

This is just a quick example of deploying a VM using a template

Event ID 2213: Active Directory DFS Replication stopped

I have been seeing intermitent issues with DFS replication on multiple DC’s across our diffrent forest. This has lead to issue with group policy replication below is a copy of the event log.

The DFS Replication service stopped replication on volume C:. This occurs when a DFSR JET database is not shut down cleanly and Auto Recovery is disabled. To resolve this issue, back up the files in the affected replicated folders, and then use the ResumeReplication WMI method to resume replication. 

Additional Information: 

Volume: C: 


Recovery Steps 

1. Back up the files in all replicated folders on the volume. Failure to do so may result in data loss due to unexpected conflict resolution during the recovery of the replicated folders. 

2. To resume the replication for this volume, use the WMI method ResumeReplication of the DfsrVolumeConfig class. For example, from an elevated command prompt, type the following command: 

wmic /namespace:\\root\microsoftdfs path dfsrVolumeConfig where volumeGuid=”GUID” call ResumeReplication

To fix the issue we have had to logon to each DC and run the above wmic command to re-eneable, this is time consuming and also requires some form of alerting to know that the replication has stopped. I decided to see if I could script this and came up with a script to search each domain and then run remote Get-wmiobject and call the resume command method. The script is set to automatically run using a schedualed task. Link to technet script is below.

DFS script on Technet

Technet is being retired and content deleted,  so I have moved this script to github.


Scan Annoymus FTP Script

I needed to scan all my subnets for FTP’s that had annoymus FTP access enabled. Below is the script I used this requires nmap.

Nmap can be downloaded from this site: https://nmap.org/download.html

I useally add nmap as a system variable so I can call the exe without specifying path.

  1. From the desktop, right click on This PC and then click “properties”.
  2. In the System Properties window, click the “Advanced” tab.
  3. Click the “Environment Variables” button.
  4. Choose path from the system variables section, then hit edit.
  5. Add a semi-colon and then your Nmap directory (e.g. C:\Program Files (x86)\Nmap) to the end of the value.

You can add or remove subnets ranges by changing the subnets variable (which is marked as red in the script below). The range can be specified by setting the range variable this can be set from 1 to 254 to restrict the scan. Below are two examples:

Example for a Single range
.\FTPCheck.ps1 -exportpath c:\temp -Range 20 -subnets 192.168.0.
Example for Multiple ranges
.\FTPCheck.ps1 -exportpath c:\temp -Range 254 -subnets 192.168.0.,10.10.10.
Scan for Open FTP sites on subnets
The script will run through each address on the specified subnets
and scan for any open FTP sites and output any sites to a csv files
.PARAMETER exportpath
The export parameter is used to specify the export path location.
This parameter is used to set the scan range this can be set from 1 to 254 depending of
how much of the subnet range needs to be scanned.
This parameter is used to specify the subnets to be scanned.
.\FTPCheck.ps1 -exportpath c:\temp -Range 20 -subnets 192.168.0.
.\FTPCheck.ps1 -exportpath c:\temp -Range 20 -subnets 192.168.0.,10.10.10.
Scan-FTP -exportpath c:\temp\export -Range 254
This script requires nmap to check for Open FTP sites
param (
[parameter(Mandatory = $true)]
[parameter(Mandatory = $true)]
[parameter(Mandatory = $true)]
if(Test-Path $exportpath){
foreach($subnet in $subnets){
$i = 1
while($i -le $Range){
$results = $subnet + $i
foreach ($result in $results)
$report = @()
Write-Host “Checking IP” $result -ForegroundColor DarkGreen
$Scan = nmap -p 21 -v –open –script ftp-anon $result -A
$report1 = $Scan | Select-String -Pattern “Nmap scan report for “
$report2 = $Scan | Select-String -Pattern “21/tcp open ftp”
$report3 = $Scan | Select-String -Pattern “(FTP code 230)”
$report4 = $Scan | Select-String -Pattern “MAC Address:”
$Properties = @{
“FTP Site” = $report1
“FTP Site Responses” = $report2
“FTP Site Access” = $report3
“FTP MAC” = $report4
$report += New-Object psobject -Property $properties
if ($report1 -ne $null){
$report | select “FTP Site”,”FTP Site Responses”,”FTP Site Access”,”FTP MAC” | Export-Csv “$exportpath\FTPScan.csv” -Append -NoClobber -NoTypeInformation
Write-Warning “FTP site responded”
Write-Warning “Path does not exist”