STEP ONE: Create Password file
In order to store the password for reference later, we’ll need to read in the password and store it securely using this methodology:
read-host -assecurestring | convertfrom-securestring | out-file C:\securestring.txt
Now we have our password stored in a secure file for later reference:
Further Reading on the cmdlets used:
Link – https://technet.microsoft.com/en-us/library/ee176935.aspx
Link – https://msdn.microsoft.com/en-us/powershell/reference/5.0/microsoft.powershell.security/convertfrom-securestring
Caution: Remember that this file contains your password. Make sure to secure it so that no one else has access to use it.
STEP TWO: Credential Configuration
For connecting to Office 365, we’ll need to User name in addition to the password created in Step One. First we can store the user name of the account connecting to PowerShell in the $UserName variable. The we can read the password file and store that in a variable. Taking these two bits of information, we can then create a variable to store proper credentials for logging into Office 365’s PowerShell URLs.
$username = "damian8192@scoles.onmicrosoft.com" $password = cat C:\securestring-scolesfamily.txt | convertto-securestring $LiveCred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $password
** Note ** One thing to remember is that PowerShell cmdlets that are revealed are limited by Microsoft’s RBAC. Microsoft has provided documentation on this with on premises solutions as well as cloud based ones. For example, Azure PowerShell’s RBAC is documented here:
Link – https://docs.microsoft.com/en-us/azure/active-directory/role-based-access-control-manage-access-powershell
Further Reading on secure passwords
Link – https://social.technet.microsoft.com/wiki/contents/articles/4546.working-with-passwords-secure-strings-and-credentials-in-windows-powershell.aspx
STEP THREE: Variable Definitions
When it comes to static variables this script requires a few be defined in order for the script to perform as expected. For example, the script will send an email out and as such we need to define SMTP parameters like sending address, recipients and more. We’ll also keep track of the current date and time for other functions in the script.
$From = "Notifications@Domain.Com" $SMTPServer = "<IP OF SMTP SERVER>" $To = "Damian@Domain.Com" $AdminAddress = "Admin@Domain.Com" $Date = get-date -Format "MM.dd.yyyy-hh.mm-tt" $EmailDate = get-date -Format "MM.dd.yyyy"
These variables will be referenced in file creation and for email reports being sent.
STEP FOUR: Import Chart of Current Cmdlet Counts
Now this step is a bit out of order as we need to create a chart before we can import it. However, let’s assume that you created the chart at the end of the last blog post and can now reference it for this PowerShell script. Keep in mind that the list of cmdlets is not comprehensive, but concentrated on the most common workloads This chart contains the count of cmdlets for each PowerShell Module that was found the last time the script was run
# Read in CSC file for comparison $CSV = Import-CSV 'c:\Downloads\PowershellCmdlets\Historical\CurrentChart.csv' Foreach ($line in $CSV) { $ExchangeNum = $Line.ExchangeOnline $ADSyncNum = $Line.ADSync $AzureNum = $Line.Azure $AzureStorageNum = $Line.AzureStorage $AzureRMNum = $Line.AzureRM $MicrosoftNum = $Line.Microsoft $MSOLNum = $Line.MSOnline $SaCNum = $Line.SecurityAndCompliance $SkypeNum = $Line.Skype }
The order of connections is not important. What is important is that all of the services offered by Office 365 be connected to. Now all the values for previously found cmdlet counts are stored in variables to be referenced below.
STEP Five: Connecting to Each Service
First we’ll make a connect to our tenant on the https://ps.outlook.com/powershell/ URL. There are a few modules that exist off this particular PowerShell URL. Once we connect to this URL, we can get a list of cmdlets with the below modules:
- ADSync
- Azure
- Azure Storage
- AzureRM
- Microsoft
- Exchange Online
Making the connection
With the two one-liners below, we leverage the credentials stored above in the $Livecred variable:
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell/ -Credential $LiveCred -Authentication Basic -AllowRedirection Import-PSSession $Session
Once the connection is made, we can list the cmdlets and see the various cmdlets respective Modules:
The same list can be sorted by ModuleName to provide a cleaner sense of what is available in terms of modules:
get-command |Sort-Object 'modulename'
To keep track of how many cmdlets there are per module, we can use the following method:
$Service = 'ADSync' $NewADSyncNum = (get-command | where {$_.modulename -eq $Service}).count
Each of the above modules cmdlet counts can be gathered using the same method.:
$NewAzureNum = (get-command | where {$_.modulename -eq $Service}).count $NewAzureStorageNum = (get-command | where {$_.modulename -eq $Service}).count $NewAzureRMNum = (get-command | where {$_.modulename -like $Service}).count $NewMicrosoftNum = (get-command | where {$_.modulename -like $Service}).count
However, Exchange Online is not listed as a valid Module Name. How do we find the Module name for Exchange cmdlets? If we look at the initial connection to Office 365, we’ll see that there is a Module Name provided:
Just looking at the module name, it’s apparent that the name is dynamic and thus temporary. This means that a script that uses that name, will have a hard time the next time its run as that module name will surely be different. How can we isolate just that one set of modules then if the name is dynamic? Maybe there is another criteria that we can filter by to find Exchange cmdlets?
Taking another look at the full list of cmdlets we see that there is only one module name that matches the ModuleType of ‘Script’ and that is the one for Exchange Online cmdlets. This then leaves us to code a more complex way to filter out these cmdlets.
$ModuleName = (Get-Module | where {$_.ModuleType -eq "Script"}).name foreach ($Name in $ModuleName) { if ($Name -like "tmp*") { $NewExchangeNum = (Get-Command | where {$_.ModuleName -eq $Name}).Count } }
Below is a summary of variables in user to keep track of the cmdlet counts for each module:
We’ve now gathered all we want from this PowerShell URL. We should close our connection:
# Cleanup - Skype Online Get-PsSession | Remove-PSSession
Once that connection is closed we can open a new one. The next one we can connect to is ‘MSOnline’:
Import-Module MsOnline Connect-MsolService -Credential $LiveCred
Once connected we can gather cmdlet counts:
$NewMSOLNum = (get-command | where {$_.modulename -like 'MSOnlin*'}).count
Then close the session:
# Cleanup - MSOnline Connection Get-PsSession | Remove-PSSession
Then once that connection is closed we can open a new one. The next one we can connect to is the Security and Compliance Center:
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.compliance.protection.outlook.com/powershell-liveid/ -Credential $LiveCred -Authentication Basic -AllowRedirection Import-PSSession $Session
Once connected we can gather cmdlet counts:
$NewSaCNum = (get-command | where {$_.modulename -eq $Name}).count
Then close the session:
# Cleanup - Security and Compliance Connection Get-PsSession | Remove-PSSession
Next we’ll connect to Skype Online:
Import-Module SkypeOnlineConnector $sfboSession = New-CsOnlineSession -Credential $LiveCred Import-PSSession $sfboSession
Once connected we can gather cmdlet counts:
$NewSkypeNum = (Get-Command | where {$_.ModuleName -eq $Name}).Count
Then close the session:
# Cleanup - SkypeOnline Connection Get-PsSession | Remove-PSSession
Finally we’ll connect to SharePoint Online:
$orgName="<Office 365 Tenant Name>" Connect-SPOService -Url https://$orgName-admin.sharepoint.com -Credential $LiveCred
Once connected we can gather cmdlet counts:
$orgName="Scoles" Connect-SPOService -Url https://$orgName-admin.sharepoint.com -Credential $LiveCred $Service = "Microsoft.Online.SharePoint.PowerShell" $NewSharepointNum = (Get-Command | where {$_.ModuleName -eq $Service}).Count Get-PsSession | Remove-PSSession
Then close the session:
# Cleanup - SharePoint Online Connection Get-PsSession | Remove-PSSession
STEP SIX: Comparing values
In this section we can compare the values we’ve stored in our cmdlet count variables to see if any changes have occurred since the script was last run. We’ll need to checked each individually using IF statements like so:
if ($NewExchangeNum -ne $ExchangeNum)
Within the loop we can keep track of which cmdlets registered changes and which did not. We do so with a variable called $Change, setting a different value if changes have occurred or not. For example, if no changes occurred, we would register this:
“AdSync Cmdlets did not change in number.”
And if cmdlet have changed, then we can register this:
“AzureStorage Cmdlets changed from $AzureStorageNum to $NewAzureStorageNum.”
Once we’ve gathered all of the changes (or no changes), the variable can be referenced in the email that is sent out as a notification.
STEP SEVEN: Current Cmdlet List
In order to know what has changed, a dump of all cmdlets for each Module is created. The file name used will have the name of the module as well as a current timestamp.
$Service = "MSOnline" get-command | where {$_.ModuleName -like "msonlin*"} | select-object name > "c:\downloads\PowerShellCmdlets\$Service-$Date.txt"
STEP EIGHT: Comparing values
The last step of the script is to send an email out to
# Send email if change was made if ($MailRequired -ceq 1) { $Subject = "Some Office 365 PowerShell Cmdlets Changed on $EmailDate" $body = (Get-Content c:\Downloads\PowershellCmdlets\Historical\CurrentChanges.csv) -join '<BR>' send-mailmessage -to $To -from $From -subject $subject -bodyashtml -body $body -smtpserver $SMTPServer } Else { $Subject = "No Office 365 PowerShell Cmdlets Changed on $EmailDate" $body = "No Office 365 PowerShell Cmdlets Changed on $EmailDate." send-mailmessage -to $To -from $From -subject $subject -bodyashtml -body $body -smtpserver $SMTPServer }
STEP NINE: Full Script
All the parts from above are included in the script below. Some additional logic and information has been placed into the script to make it more useful:
# Office 365 User Name and Password $username = "<Office 365 account with appropriate rights>" $password = cat C:\securestring.txt | convertto-securestring $LiveCred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $password # Other Variables $from = "Notifications@Domain.Com" $SMTPServer = "<SMTP Server's IP Address>" $to = "Damian@Domain.Com" $AdminAddress = "Admin@Domain.Com" $Date = get-date -Format "MM.dd.yyyy-hh.mm-tt" $EmailDate = get-date -Format "MM.dd.yyyy" # Read in CSC file for comparison $CSV = Import-CSV 'c:\Scripting\PowershellCmdlets\Historical\CurrentChart.csv' Foreach ($line in $CSV) { $ExchangeNum = $Line.ExchangeOnline $ADSyncNum = $Line.ADSync $AzureNum = $Line.Azure $AzureStorageNum = $Line.AzureStorage $AzureRMNum = $Line.AzureRM $MicrosoftNum = $Line.Microsoft $MSOLNum = $Line.MSOnline $SaCNum = $Line.SecurityAndCompliance $SkypeNum = $Line.Skype $SharePointNum = $Line.SharePoint } $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell/ -Credential $LiveCred -Authentication Basic -AllowRedirection Import-PSSession $Session $ModuleName = (Get-Module | where {$_.ModuleType -eq "Script"}).name foreach ($Name in $ModuleName) { if ($Name -like "tmp*") { $NewExchangeNum = (Get-Command | where {$_.ModuleName -eq $Name}).Count Write-Host "Exchange has $NewExchangeNum Cmdlets now." $Service = "ExchangeOnline" get-command | where {$_.ModuleName -eq $Name} | select-object name > "c:\Scripting\PowerShellCmdlets\$Service-$Date.txt" } } # ADSync Cmdlets $Service = 'ADSync' $NewADSyncNum = (get-command | where {$_.modulename -eq $Service}).count Write-Host "The service $Service has $NewADSyncNum cmdlets now." get-command | where {$_.ModuleName -eq $Service} | select-object name > "c:\Scripting\PowerShellCmdlets\$Service-$Date.txt" # Azure Cmdlets $Service = 'Azure' $NewAzureNum = (get-command | where {$_.modulename -eq $Service}).count Write-Host "The service $Service has $NewAzureNum cmdlets now." get-command | where {$_.ModuleName -eq $Service} | select-object name > "c:\Scripting\PowerShellCmdlets\$Service-$Date.txt" # Azure Storage Cmdlets $Service = 'Azure.Storage' $NewAzureStorageNum = (get-command | where {$_.modulename -eq $Service}).count Write-Host "The service $Service has $NewAzureStorageNum cmdlets now." get-command | where {$_.ModuleName -eq $Service} | select-object name > "c:\Scripting\PowerShellCmdlets\$Service-$Date.txt" # AzureRM Cmdlets $Service = 'AzureRM*' $NewAzureRMNum = (get-command | where {$_.modulename -like $Service}).count Write-Host "The service $Service has $NewAzureRMNum cmdlets now." $Name = $Service.Substring(0,$Service.Length-1) get-command | where {$_.ModuleName -like $Service} | select-object name > "c:\Scripting\PowerShellCmdlets\$Name-$Date.txt" # Microsoft Cmdlets $Service = 'Microsoft*' $NewMicrosoftNum = (get-command | where {$_.modulename -like $Service}).count Write-Host "The service $Service has $NewMicrosoftNum cmdlets now." $Name = $Service.Substring(0,$Service.Length-1) get-command | where {$_.ModuleName -like $Service} | select-object name > "c:\Scripting\PowerShellCmdlets\$Name-$Date.txt" # Cleanup - Main Connection Get-PsSession | Remove-PSSession # MSOnline Import-Module MsOnline Connect-MsolService -Credential $LiveCred $NewMSOLNum = (get-command | where {$_.modulename -like 'MSOnlin*'}).count Write-Host "The MSOL Service has $NewMSOLNum Cmdlets now." $Service = "MSOnline" get-command | where {$_.ModuleName -like "msonlin*"} | select-object name > "c:\Scripting\PowerShellCmdlets\$Service-$Date.txt" # Cleanup - MSOnline Connection Get-PsSession | Remove-PSSession #Email results to me # Compliance $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.compliance.protection.outlook.com/powershell-liveid/ -Credential $LiveCred -Authentication Basic -AllowRedirection Import-PSSession $Session $ModuleName = (Get-Module | where {$_.ModuleType -eq "Script"}).name foreach ($name in $ModuleName) { if ($Name -like "tmp*") { $NewSaCNum = (get-command | where {$_.modulename -eq $Name}).count Write-Host "The Security and Complance Center has $NewSaCNum Cmdlets now." $Service = "SecurityAndCompliance" get-command | where {$_.ModuleName -eq $Service} | select-object name > "c:\Scripting\PowerShellCmdlets\$Service-$Date.txt" } } # Cleanup - Compliance Connection Get-PsSession | Remove-PSSession # Skype Online Import-Module SkypeOnlineConnector $sfboSession = New-CsOnlineSession -Credential $LiveCred Import-PSSession $sfboSession $ModuleName = (Get-Module | where {$_.ModuleType -eq "Script"}).name foreach ($Name in $ModuleName) { if ($Name -like "tmp*") { $NewSkypeNum = (Get-Command | where {$_.ModuleName -eq $Name}).Count Write-Host "Skype Online has $NewSkypeNum cmdlets now." $Service = "SkypeOnline" get-command | where {$_.ModuleName -eq $Name} | select-object name > "c:\Scripting\PowerShellCmdlets\$Service-$Date.txt" } } # Cleanup - Skype Online Get-PsSession | Remove-PSSession # SharePoint Online # Connect $orgName="Scoles" Connect-SPOService -Url https://$orgName-admin.sharepoint.com -Credential $LiveCred $Service = "Microsoft.Online.SharePoint.PowerShell" $NewSharepointNum = (Get-Command | where {$_.ModuleName -eq $Service}).Count Write-Host "SharePoint Online has $NewSharepointNum cmdlets now." get-command | where {$_.ModuleName -eq $Name} | select-object name > "c:\Scripting\PowerShellCmdlets\$Service-$Date.txt" # Cleanup - SharePoint Online Get-PsSession | Remove-PSSession # ReWrite the CSV File # Header $HeaderRow = "ExchangeOnline,"+"ADSync,"+"Azure,"+"AzureStorage,"+"AzureRM,"+"Microsoft,"+"MSOnline,"+"SecurityAndCompliance,"+"Skype,"+"SharePoint" $HeaderRow > 'c:\Scripting\PowershellCmdlets\Historical\CurrentChart.csv' # New numbers $NewRow = "$NewExchangeNum," + "$NewADSyncNum," + "$NewAzureNum," + "$NewAzureStorageNum," + "$NewAzureRMNum," + "$NewMicrosoftNum," + "$NewMSOLNum," + "$NewSaCNum," + "$NewSkypeNum," + "$NewSharePointNum" Add-Content 'c:\Scripting\PowershellCmdlets\Historical\CurrentChart.csv' $NewRow # Add row to historical data $NewRow = "$Date,"+"$NewExchangeNum," + "$NewADSyncNum," + "$NewAzureNum," + "$NewAzureStorageNum," + "$NewAzureRMNum," + "$NewMicrosoftNum," + "$NewMSOLNum," + "$NewSaCNum," + "$NewSkypeNum," + "$NewSharePointNum" Add-Content 'c:\Scripting\PowershellCmdlets\Historical\FullChart.csv' $NewRow # Email if ($NewExchangeNum -ne $ExchangeNum) { $change = "ExchangeOnline Cmdlets changed from $ExchangeNum to $NewExchangeNum." Add-Content 'c:\Scripting\PowershellCmdlets\Historical\CurrentChanges.csv' $Change $MailRequired = 1 } Else { $NoChange = "ExchangeOnline Cmdlets did not change in number." Add-Content 'c:\Scripting\PowershellCmdlets\Historical\CurrentChanges.csv' $NoChange } if ($NewADSyncNum -ne $ADSyncNum) { $change = "AdSync Cmdlets changed from $ADSyncNum to $NewADSyncNum." Add-Content 'c:\Scripting\PowershellCmdlets\Historical\CurrentChanges.csv' $Change $MailRequired = 1 } Else { $NoChange = "AdSync Cmdlets did not change in number." Add-Content 'c:\Scripting\PowershellCmdlets\Historical\CurrentChanges.csv' $NoChange } if ($NewAzureNum -ne $AzureNum) { $change = "Azure Cmdlets changed from $AzureNum to $AzureNum." Add-Content 'c:\Scripting\PowershellCmdlets\Historical\CurrentChanges.csv' $Change $MailRequired = 1 } Else { $NoChange = "Azure Cmdlets did not change in number." Add-Content 'c:\Scripting\PowershellCmdlets\Historical\CurrentChanges.csv' $NoChange } if ($NewAzureStorageNum -ne $AzureStorageNum) { $change = "AzureStorage Cmdlets changed from $AzureStorageNum to $NewAzureStorageNum." Add-Content 'c:\Scripting\PowershellCmdlets\Historical\CurrentChanges.csv' $Change $MailRequired = 1 } Else { $NoChange = "AzureStorage Cmdlets did not change in number." } if ($NewAzureRMNum -ne $AzureRMNum) { $change = "AzureRM Cmdlets changed from $AzureRMNum to $NewAzureRMNum." Add-Content 'c:\Scripting\PowershellCmdlets\Historical\CurrentChanges.csv' $Change $MailRequired = 1 } Else { $NoChange = "ExchangeOnline Cmdlets did not change in number." Add-Content 'c:\Scripting\PowershellCmdlets\Historical\CurrentChanges.csv' $NoChange } if ($NewMicrosoftNum -ne $MicrosoftNum) { $change = "Microsoft Cmdlets changed from $MicrosoftNum to $NewMicrosoftNum." Add-Content 'c:\Scripting\PowershellCmdlets\Historical\CurrentChanges.csv' $Change $MailRequired = 1 } Else { $NoChange = "AzureRM Cmdlets did not change in number." Add-Content 'c:\Scripting\PowershellCmdlets\Historical\CurrentChanges.csv' $NoChange } if ($NewMSOLNum -ne $MSOLNum) { $change = "MSOnline Cmdlets changed from $MSOLNum to $NewMSOLNum." Add-Content 'c:\Scripting\PowershellCmdlets\Historical\CurrentChanges.csv' $Change $MailRequired = 1 } Else { $NoChange = "MSOnline Cmdlets did not change in number." Add-Content 'c:\Scripting\PowershellCmdlets\Historical\CurrentChanges.csv' $NoChange } if ($NewSaCNum -ne $SaCNum) { $change = "Security and Compliance Cmdlets changed from $SaCNum to $NewSaCNum." Add-Content 'c:\Scripting\PowershellCmdlets\Historical\CurrentChanges.csv' $Change $MailRequired = 1 } Else { $NoChange = "Security and Compliance Cmdlets did not change in number." Add-Content 'c:\Scripting\PowershellCmdlets\Historical\CurrentChanges.csv' $NoChange } if ($NewSkypeNum -ne $SkypeNum) { $change = "SkypeOnline Cmdlets changed from $SkypeNum to $NewSkypeNum." Add-Content 'c:\Scripting\PowershellCmdlets\Historical\CurrentChanges.csv' $Change $MailRequired = 1 } Else { $NoChange = "SkypeOnline Cmdlets did not change in number." Add-Content 'c:\Scripting\PowershellCmdlets\Historical\CurrentChanges.csv' $NoChange } if ($NewSharePointNum -ne $SharePointNum) { $change = "SharePointOnline Cmdlets changed from $SharePointNum to $NewSharePointNum." Add-Content 'c:\Scripting\PowershellCmdlets\Historical\CurrentChanges.csv' $Change $MailRequired = 1 } Else { $NoChange = "SharePointOnline Cmdlets did not change in number." Add-Content 'c:\Scripting\PowershellCmdlets\Historical\CurrentChanges.csv' $NoChange } # Send email if change was made if ($MailRequired -ceq 1) { $Subject = "Some Office 365 PowerShell Cmdlets Changed on $EmailDate" $body = (Get-Content c:\Scripting\PowershellCmdlets\Historical\CurrentChanges.csv) -join '<BR>' send-mailmessage -to $To -from $From -subject $subject -bodyashtml -body $body -smtpserver $SMTPServer } Else { $Subject = "No Office 365 PowerShell Cmdlets Changed on $EmailDate" $body = "No Office 365 PowerShell Cmdlets Changed on $EmailDate." send-mailmessage -to $To -from $From -subject $subject -bodyashtml -body $body -smtpserver $SMTPServer } # Final Cleanup Get-PSSession | Remove-PSSession Remove-Item "C:\Scripting\PowerShellCmdlets\historical\CurrentChanges.csv"
Email results
No Changes
Changes
Final Steps
The last step is to schedule the script. This is outside the scope of the blog post, but you can easily follow steps provided:
Here – https://blogs.technet.microsoft.com/heyscriptingguy/2012/08/11/weekend-scripter-use-the-windows-task-scheduler-to-run-a-windows-powershell-script/
OR
Here – https://dmitrysotnikov.wordpress.com/2011/02/03/how-to-schedule-a-powershell-script/