There are many ways to monitor Windows services but one of the most flexible methods is via PowerShell (PS). The advanced PS scripts referenced in this post will help you detect when a Windows service is stopped, when it is back up again, and will ultimately send an email alert with details. To help automate monitoring, you can configure PS to schedule tasks to run scripts daily, at different intervals.
The few critical Windows OS services are started when the server boots and are stopped when the server shuts down. These services need to be continuously running and they don’t have a reason to stop their operations while the server is up. But on the other hand, there are applications such as IIS, Exchange, Active Directory that are running on a Windows server.
These applications need their services to be up and running but, if any of these services stop, the whole application is disrupted. A Windows Services monitoring solution should be able to keep track of these services and send alerts when one of these services stops or is down. There are some Windows Services monitoring solutions that can help you automate this process. These tools include PRTG Network Monitor, and others.
But if you have basic PowerShell scripting skills, you can set up your own monitoring solution with the help of basic functions and cmdlets.
Related Post: PowerShell Kill Process Command
Managing Services with Cmdlets
A cmdlet is a lightweight Windows PowerShell (PS) script that helps interact with the PS platform and the scripting language.
There are eight service cmdlets that let you perform an extensive number of tasks on Windows services.
These can let you query, reset, start, stop, and set services.
To get a list of the cmdlets, you can open PowerShell and use the command:
Get-Help \*-Service
We will be using the “Get-Service” cmdlet to display the status of a service named Microsoft Remote Access Service (RasMan).
For example, let’s say that currently, RasMan is down.
Get-Service -Name RasMan | Select-Object -Property *
As you can see from the output, the current “Status” of the service is “Stopped.”
From this output, you can also see other details like whether you can start it, stop it, reset it, its dependent services, type, etc.
The Get-Service is the right cmdlet to monitor services, but you can also use others to push actions such as the “Start-Service” or “Restart-Service.”
Let’s go ahead and attempt to start the service using the “Start-Service” cmdlet.
Start-Service -Name RasMan
And now from the results shown above, you can see that the service is up and running.
Now, the good thing with PowerShell is that you can combine the results of a “Get-Service” with the actions of the “Start-Service”.
You can use this combination of both to query and then start a service if it is equal to “stopped.”
Get-Service RasMan | %{if ($_.Status -eq "Stopped") { Start-Service RasMan }}
Cmdlets are amazing lightweight tools that can help monitor services running on a local or remote server.
But these tools will not be capable of notifying you when a service status changes or for automating specific tasks.
The next advanced PowerShell functions will take this simple script to a new level.
Advanced PowerShell Functions
The functions shown here are able to keep track of a Windows service, attempt to restart, and even send an alert via email.
The Get-PSServiceStatus Function:
The open-source PowerShell code Get-PSServiceStatus developed by dfranciscus, suggests using the Send-MailMessage function, along with the cmdlets.
This script is capable of sending alerts via email when the service changes.
The Get-PSServiceStatus can only keep track of the last service status polled and notify you when there is a change.
To accomplish this, the script uses a text file.
If a service is down, the code creates the text file with detailed information and sends an alert.
When the service is back up, it deletes the text file.
The following is the overall flow of the function:
- Check for a text file and the services.
- If there is a text file, it means that the service was not running before.
- If the service is not running and there is no text file, create a text file.
- Send an email alert saying that the service is down.
- If the service is running again, remove the text file.
- Send an email saying that the service is up.
To run the function you’ll need to specify the computer name (remote or local), the service name, and path name where the text files will be stored.
Other important parameters to specify are, the source email and destination email.
Download the Source Code:
The Get-PSServiceStatus.ps1 can be downloaded here:
https://github.com/dfranciscus/Get-PSServiceStatus/blob/master/Get-PSServiceStatus.ps1
Other Functions
Below are two other PowerShell functions that can help you monitor Windows services, from the Microsoft web portal TechNet:
- Check-Svc Function
Another great script used to monitor Windows Services via PowerShell is Microsoft’s check-Svc. The script finds a stopped service with Get-Service cmdlet, and attempts to restart it. If after the third time, it is unable to start, it will send an alert via email. - CheckServices Function
The Checkservices is another PS script that allows you to monitor the Windows Services of local and remote computers. This script will check the service status and report back in HTML all services that are running or stopped. It will also send an alert via email if the services are stopped.
Scheduling a Task to Run these PowerShell Functions
To automate the monitoring even more, you can use the Microsoft Windows Task Scheduler to automatically launch a PowerShell script, such as Get-PSServiceStatus function, at a certain time or when specific conditions are met.
C:\Windows\System32\schtasks.exe /create /TN MonitorServ /ST 12:30 /SC DAILY /RI 10 /TR “powershell.exe C:\ Get-PSServiceStatus”
This newly created Windows Task Schedule will run the Get-PSServiceStatus function daily starting at 12:30 and every 10 minutes.
Final Words & Conclusion
A simple PowerShell script with two cmdlets can help you start a Windows service if it is stopped, much like the built-in Windows Services “Automatic Startup” function.
Throw in a scheduling task and you’ll have a simple polling system.
But when you start to combine it with other functions such as the “Send-MailMessage,” and advanced polling mechanisms such as the one in Get-PSServiceStatus, you can take your Windows Services monitoring to the next level.
Monitor Windows Services via PowerShell FAQs
Can I use PowerShell to monitor services on remote computers?
You can use PowerShell to monitor services on remote computers by specifying the computer name when running the cmdlets. For example, you can use the “-ComputerName” parameter with the “Get-Service” cmdlet to view the status of a specific service or all services on a remote computer.
Can I schedule a PowerShell script to monitor services regularly?
Yes, you can use the “Scheduled Tasks” feature in Windows or use 3rd party software to schedule a PowerShell script to run regularly. The script could include commands to check the status of specific services, and then send an email or generate a log file if the status of service changes.
Can I filter or sort the services that are displayed by the “Get-Service” cmdlet?
Yes, you can use the “-Name” parameter to filter the services displayed by name and use the “-Status” to filter the services displayed by status. You can use the “-Sort” parameter to sort the services based on properties such as name, status, and start mode.
Benefits of using a progress bar in PowerShell
1. Real-time Visual feedback
2. Time estimation to Track progress
A progress bar can also provide an estimate of how long your script will take to complete. This estimate can be incredibly helpful, especially when running scripts that take a long time to complete. It allows you to plan your time accordingly and ensures that you don’t waste time waiting for your script to finish.
3. Improved user experience
Understanding the Write-Progress cmdlet in PowerShell
Write-Progress is a cmdlet in PowerShell that allows you to display a progress bar in the console. It has several parameters that allow you to customize the appearance of the progress bar, including the percentage complete, the status message, and the activity name. Here are the parameters that you can use with Write-Progress:
Basic Write-Progress syntax looks like:
Write-Progress [-Activity] <String> [[-Status] <String>] [-PercentComplete <Int32>] [-SecondsRemaining <Int32>] [-Id <Int32>] [-ParentId <Int32>] [-Completed] [-SourceId <Int32>] [-CurrentOperation <String>] [<CommonParameters>]
Here are the important parameters of the Write-Progress cmdlet:
Parameter | Description |
---|---|
Activity | The Activity parameter Specifies the title of the progress bar. This will appear as the first line of text. |
Status | This helps to display the current state of the activity or status of a running command in the status bar of the progress bar |
PercentComplete | 0 to 100 percent done |
SecondsRemaining | Countdown timer |
Id | Unique ID to update specific bar |
ParentId | Group child progress bars |
Write-Progres Example
- Use the
Write-Progress
cmdlet to create a new progress bar. - Specify the parameters, such as
-Activity
,-Status
,-PercentComplete
,-SecondsRemaining
, and-CurrentOperation
to customize the progress bar. - Update the progress bar periodically using the
Write-Progress
cmdlet with updated values for-PercentComplete
. - When the task is complete, use the
Complete
parameter to finish the progress bar.
To continually update the progress bar, we must place Write-Progress calls at certain points within your script’s logic, incrementing the PercentComplete each time. Here’s an example of a PowerShell script that uses the Write-Progress cmdlet to display a progress bar:
$Collection = 1..100 ForEach ($Item in $Collection) { Write-Progress -PercentComplete ($Item/100*100) -Status "Processing Items" -Activity "Item $item of 100" # Your actual script logic here Start-Sleep -Milliseconds 50 }
And here is what it looks like in PowerShell ISE:
Using Write-Progress in PowerShell is relatively straightforward. Here’s a step-by-step guide on how to use it:
Step 1: Define the variables
The first step is to define the variable or collection that you will be using in your script. These variables will have the collection of data set you’ll be processing.
Step 2: Start the progress bar and Perform Operation
The next step is to start the progress bar using the Write-Progress cmdlet and perform your target operation. You will need to specify the activity name and the status message.
Step 3: Update the progress bar
A common way to use Write-Progress is within a For loop in your script.
For($i=1; $i -le 100; $i++){ Write-Progress -Activity "Processing Files" -Status "$i% Complete" -PercentComplete $i # Perform loop action sleep -Milliseconds 100 }
This will iterate from 1 to 100, updating the progress bar each time with the current percent complete. You can update the status message to provide more context on which loop iteration is running.
Step 4: Complete the progress bar
Once your script has finished running, you have to complete the progress bar using the Write-Progress cmdlet. This will ensure that the progress bar is removed from the console.
Progress Bar with Countdown Timer
To show a countdown timer in the progress bar, use the SecondsRemaining
parameter:
Write-Progress -Activity "Installing" -Status "Time Remaining" -SecondsRemaining $timeLeft
As $timeLeft decreases, the progress bar will visually count down the estimated time left. Here is a real-time example:
$progressParams = @{ Activity = "Processing data" Status = "In progress" PercentComplete = 0 SecondsRemaining = 60 CurrentOperation = "Initializing" } Write-Progress @progressParams $data = 1..10000 $index = 1 # Start the countdown timer $timer = [Diagnostics.Stopwatch]::StartNew() # Loop through the data and update the progress bar foreach ($item in $data) { # Perform some operation on $item # Update the progress bar $progressParams.PercentComplete = ($index / $data.Count) * 100 $progressParams.CurrentOperation = "Processing item $index of $($data.Count)" $progressParams.SecondsRemaining = (($timer.Elapsed.TotalSeconds / $index) * ($data.Count - $index)).ToString("F0") Write-Progress @progressParams $index++ } # Complete the progress bar Write-Progress -Completed -Activity "Completed"
Here is what it looks like in PowerShell ISE:
Here are a few more examples of Count down timers using the PowerShell Write-Progress cmdlet:
Function Start-Countdown { param ( [Parameter(Mandatory=$true)] [int]$Seconds ) # Countdown start time $StartTime = Get-Date $ElapsedTime =0 # While there are still seconds left While ($ElapsedTime -lt $seconds) { # Calculate elapsed time $ElapsedTime = [math]::Round(((Get-Date) - $StartTime).TotalSeconds,0) # Update the progress bar Write-Progress -Activity "Counting down..." -Status ("Time left: " + ($Seconds - $ElapsedTime) + " seconds") -PercentComplete (($elapsedTime / $Seconds) * 100) #$Seconds = $Seconds - $elapsedTime # Wait for a second Start-Sleep -Seconds 1 } # Once countdown is complete, close the progress bar Write-Progress -Activity "Counting down..." -Completed } # Call the function to start a 10-second countdown Start-Countdown -Seconds 10
Here is another example of how to show the Progress Bar in While Loop:
# Set total time for countdown timer $timeRemaining = 20 while($timeRemaining -gt 0){ # Calculate percentage $percentComplete = (($timeRemaining / 60) * 100) # Update progress bar Write-Progress -Activity "Deployment in progress" -Status "Time remaining: $timeRemaining seconds" -PercentComplete $percentComplete -SecondsRemaining $timeRemaining # Decrement timer $timeRemaining-- # Wait 1 second Start-Sleep -Seconds 1 }
Using progress bars in PowerShell ForEach loops
Using a progress bar in a foreach loop can be incredibly helpful, especially when iterating over a large number of items. Here’s an example of how to use a progress bar in a foreach loop:
$Items = 1..10000 Write-Progress -Activity "Processing Items" -Status "Starting" -PercentComplete 0 foreach ($Item in $Items) { $PercentComplete = (($Item / $Items.Count) * 100) $Status = "Processing Item $($Item)" Write-Progress -Activity "Processing Items" -Status $Status -PercentComplete $PercentComplete # Do some processing } Write-Progress -Activity "Processing Items" -Status "Complete" -PercentComplete 100
Using Progress Bar with Multiple Operations
For extensive scripts involving multiple operations, you can segment the progress bar:
Write-Progress -Activity "Main Task" -Status "Task 1 of 3" -PercentComplete 33 # Task 1 logic Start-Sleep -Seconds 1 Write-Progress -Activity "Main Task" -Status "Task 2 of 3" -PercentComplete 66 # Task 2 logic Start-Sleep -Seconds 1 Write-Progress -Activity "Main Task" -Status "Task 3 of 3" -PercentComplete 100 # Task 3 logic Start-Sleep -Seconds 1
Nested (Parent-Child) Progress Bars for multi-level Operations
Parent-child progress bars are especially useful when you have a multi-level process. Here’s an example of a parent-child progress bar using Write-Progress. Let’s consider you’re processing multiple files, and for each file, you have several tasks to complete:
# Parent loop - processing files 1..5 | ForEach-Object { $fileNumber = $_ # Parent progress Write-Progress -Activity "Processing Files" -Status "File $fileNumber of 5" -PercentComplete (($fileNumber / 5) * 100) -id 0 # Simulate some file-specific tasks in a child loop 1..10 | ForEach-Object { $taskNumber = $_ # Child progress (nested within the parent) Write-Progress -Activity "Performing Task on File $fileNumber" -Status "Task $taskNumber of 10" -PercentComplete (($taskNumber / 10) * 100) -ParentId 0 # Simulating time taken for each task with sleep Start-Sleep -Milliseconds 100 } } # Completing the parent progress bar Write-Progress -Activity "Processing Files" -Completed -Status "All files processed!"
The -ParentId parameter in Write-Progress for the child progress bar tells PowerShell that this progress bar is a child of another progress bar. The ID 0 typically refers to the most recent parent Write-Progress bar. Output:
When you run the above script, you’ll see a top-level progress bar for file processing and a nested progress bar for tasks on each file.
Progress Bar GUI in PowerShell
For GUI-based progress bars, the Windows.Forms.ProgressBar .NET control can be used.
Here is a sample script:
Add-Type -AssemblyName System.Windows.Forms $progressForm = New-Object System.Windows.Forms.Form $progressForm.Width = 300 $progressForm.Height = 150 $progressForm.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedSingle $progressForm.Text = "Processing data..." $progressBar = New-Object System.Windows.Forms.ProgressBar $progressBar.Location = New-Object System.Drawing.Point(10, 50) $progressBar.Size = New-Object System.Drawing.Size(280, 20) $progressForm.Controls.Add($progressBar) $progressLabel = New-Object System.Windows.Forms.Label $progressLabel.Location = New-Object System.Drawing.Point(10, 20) $progressLabel.Size = New-Object System.Drawing.Size(280, 20) $progressLabel.Text = "0% Complete" $progressLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter $progressForm.Controls.Add($progressLabel) $progressForm.Show() $data = 1..100 $index = 1 # Loop through the data and update the progress bar foreach ($item in $data) { # Perform some operation on $item # Update the progress bar $progressPercent = ($index / $data.Count) * 100 $progressBar.Value = $progressPercent $progressLabel.Text = "$progressPercent% Complete" Start-Sleep -Milliseconds 100 $index++ } $progressForm.Close()
This creates a configurable progress bar by setting properties like minimum, maximum, and step values. The control can then be updated by incrementing $ProgressBar.Value
in your script.
Practical Examples of Write-Progress in PowerShell
Let’s take a look at some examples of how to use Write-Progress in PowerShell:
Example 1: Copy Files with Progress Bar
In this example, we are using Write-Progress to display the progress of a script that is copying files from one location to another.
$Files = Get-ChildItem -Path "C:\Source" $Destination = "C:\Destination" $TotalFiles = $Files.Count $Count = 0 Write-Progress -Activity "Copying Files" -Status "Starting" -PercentComplete 0 foreach ($File in $Files) { $Count++ $PercentComplete = (($Count / $TotalFiles) * 100) $Status = "Copying $($File.Name)" Write-Progress -Activity "Copying Files" -Status $Status -PercentComplete $PercentComplete Copy-Item $File.FullName $Destination -Force } Write-Progress -Activity "Copying Files" -Status "Complete" -PercentComplete 100
Example 2: Download Files with Progress Bar
In this example, we are using Write-Progress to display the progress of a script that downloads files from the internet.
$Urls = @( "https://www.microsoft.com/downloads/SharePointSP1.msi", "https://www.microsoft.com/downloads/SharePointSP2.msi", "https://www.microsoft.com/downloads/SharePointSP3.msi" ) # Destination folder $destinationFolder = "C:\Downloads" # Ensure the destination folder exists if (-not (Test-Path $destinationFolder)) { New-Item -Path $destinationFolder -ItemType Directory } # Download each file $totalUrls = $urls.Count for ($i=0; $i -lt $totalUrls; $i++) { $url = $urls[$i] $fileName = [System.IO.Path]::GetFileName($url) # Extract file name from URL $destinationPath = Join-Path -Path $destinationFolder -ChildPath $fileName # Display main progress Write-Progress -Activity "Downloading files" -Status ("Downloading " + $fileName) -PercentComplete (($i / $totalUrls) * 100) # Download file with sub-progress bar for individual file download Invoke-WebRequest -Uri $url -OutFile $destinationPath -Verbose } Write-Progress -Activity "Downloading files" -Completed -Status "All files downloaded!" Write-Host "All files downloaded successfully!" -ForegroundColor Green
Best practices for using progress bars in PowerShell
- Only use a progress bar when necessary: While progress bars can be helpful, they can also slow down your script if overused. Only use a progress bar when necessary. If a task finishes instantly or in a very short time frame, showing a progress bar might be unnecessary and even counterproductive, as it could flash on and off too quickly for the user to see.
- Update the progress bar frequently: Update the progress bar frequently to provide real-time feedback to the user. This will ensure that the user knows that the script is running and making progress.
- Use clear and concise status messages: Use the -Activity and -Status parameters to provide clear and concise information about what’s happening. Use the -CurrentOperation parameter to offer additional context, especially if it helps users understand the current task.
- Calculate Percentages Correctly: Ensure that your -PercentComplete value is accurately calculated. It should range from 0 to 100, representing the completion percentage.
- Use Nested Progress Bars for Multi-Level Tasks: If your script has tasks within tasks (like copying multiple folders where each folder has multiple files), use parent-child progress bars. Use the -Id and -ParentId parameters to manage and display nested progress bars correctly.
- Complete the Progress Bar: Use the -Completed switch to signal the end of the task, ensuring that the progress bar disappears or indicates completion. Also, Show progress at 0% before the operation starts.
- Test your script with large data sets: Test your script with large data sets to ensure that the progress bar is working properly and that it doesn’t slow down your script.
Troubleshooting common issues with progress bars in PowerShell
While using a progress bar in PowerShell is relatively straightforward, there are a few common issues that you may encounter. Here are a few tips for troubleshooting these issues:
1. The progress bar not updating
If the progress bar is not updating, ensure you update it frequently enough. Check your -PercentComplete calculation. Ensure that it’s updating as expected within the loop or process. For loops that process items quickly, consider adding a small delay with Start-Sleep for visual clarity.
If you are updating it frequently, and it still isn’t working, try running your script in a new PowerShell window.
2. Progress bar not displaying
If the progress bar is not displaying, ensure you start it with the Write-Progress cmdlet. If you are starting it with the Write-Progress cmdlet and it still isn’t working, ensure your loop or process is not finishing too quickly. The progress bar may not appear for very fast tasks.
3. Progress bar slowing down your script
4. Error: “Write-Progress : Cannot validate argument on parameter ‘PercentComplete’.
“The 110 is greater than the maximum allowed range of 100.”
- Cause: The value for ‘PercentComplete’ parameter exceeds the maximum limit of 100.
- Solution: Ensure the ‘PercentComplete’ parameter is within the range of 0 to 100.
Here is the Official Microsoft Documentation reference for Write-Progress cmdlet!
Conclusion and final thoughts
What is a PowerShell progress bar?
Why would I use a Progress Bar in PowerShell?
How can I add a progress bar to my PowerShell script?
Is it possible to display multiple progress bars at once?
PowerShell supports nested progress bars, allowing you to display a parent progress bar and one or more child progress bars simultaneously.
How do I update the progress bar during a loop?
Inside a loop, you can update the progress bar by calling Write-Progress
with updated values for -PercentComplete
and other parameters at each iteration.
How do I remove the progress bar once the operation is complete?
The progress bar will automatically disappear once the script is completed. You can also manually remove it by calling Write-Progress
with the -Completed
parameter. For example: Write-Progress -Activity "Processing" -Status "Completed" -Completed
I have this powershell code already created.
It runs and sends an email with details of each remote machine, service and what seems to be the status.
However the service name and status appear to not function correctly. The output from this script tells me that the service isn’t found. However, I’ve checked this by just running this on the same machine I ran this full script to validate the services are present and identify the status of the actual service;
Get-Service -ComputerName machine1 -Name MyService
My full PowerShell script is below;
# Define the list of machine names where the service should be checked
$machineNames = @("machine1","machine2") # Add more machine names as needed
# Define the service name to check
$serviceName = "MyService"
# Function to check service status on a specific machine
function Get-ServiceStatus {
param (
[string]$machineName,
[string]$service
)
$serviceStatus = Get-Service -ComputerName $machineName -Name $service -ErrorAction
SilentlyContinue
if ($serviceStatus) {
return $serviceStatus.Status
} else {
return "Service Not Found"
}
}
# Initialize an empty array to store information about services that are not running
$notRunningServices = @()
# Check the service status on each machine
foreach ($machine in $machineNames) {
$status = Get-ServiceStatus -machineName $machine -service $serviceName
if ($status -ne "Running") {
$notRunningServices += [PSCustomObject]@{
MachineName = $machine
ServiceName = $serviceName
Status = $status
}
}
}
# If there are services not running, send an email
if ($notRunningServices.Count -gt 0) {
$smtpServer = "smtp.server.com"
$smtpPort = 25
$fromAddress = "[email protected]"
$toAddress = "[email protected]"
$emailSubject = "MyService Service Status - Not Running"
$emailBody = "The following services are not running:`r`n`r`n"
foreach ($service in $notRunningServices) {
$emailBody += "Server: $($service.MachineName)`r`nService:
$($service.ServiceName)`r`nStatus: $($service.Status)`r`n`r`n"
}
Send-MailMessage -From $fromAddress -To $toAddress -Subject $emailSubject -Body $emailBody -
SmtpServer $smtpServer -Port $smtpPort
}
I need to resolve the powershell script to identify the remote service name on each machine.
A copy of the email is here;
Server: machine1
Service: MyService
Status: Service Not Found
Server: machine2
Service: MyService
Status: Service Not Found
Would really appreciate if someone could please help.
Thanks in advance
You can use the Get-Service cmdlet in PowerShell to get all services on a computer, including running and stopped services.
Often you may want to use the Get-Service cmdlet to check the status of a particular service and to start a particular service if it is not currently running.
Method 1: Check Status of Specific Service
-Name ScDeviceEnum).Status
This particular example will return the status of the specific service named ScDeviceEnum.
For example, this may return “Running”, “Stopped”, “Paused”, etc.
Method 2: Start Specific Service if Not Currently Running
This particular example will attempt to start the service named ScDeviceEnum if it is not currently running.
Suppose that we would like to check if the service named ScDeviceEnum is currently running on our computer.
-Name ScDeviceEnum).Status
This returns Stopped, which tells us that this particular service is not currently running.
If the service is currently stopped, then this script will return Service is Stopped.
It will then attempt to start the service and then output the results of whether or not we were able to successfully start the service.
If there is an error when attempting to start the service, the error message will appear.
Lastly, the script will output Operation complete to indicate that the script is finished.
Note: You can find the complete documentation for the Get-Service cmdlet in PowerShell here.
PowerShell: How to Use Get-Service and Filter Results
PowerShell: How to Filter for Unique Objects
PowerShell: How to Use Get-ChildItem with Filter
PowerShell: How to Use Get-ChildItem with Multiple Filters
Key takeaways
- Automated compliance check: The script automates the process of checking Windows activation and licensing status, ensuring compliance across multiple systems.
- Error identification and resolution: It identifies specific Windows activation errors and provides troubleshooting steps, aiding in quick resolution.
- Efficiency in IT management: Automates a traditionally manual process, saving time and resources for IT professionals and MSPs.
- Supports legal and security standards: Helps maintain legal licensing requirements and ensures systems receive necessary updates for security.
- Versatile for modern Windows systems: Designed for Windows 10 and Windows Server 2016 onwards, making it suitable for contemporary IT environments.
- Integration with IT management tools: Can be incorporated into broader IT management solutions like NinjaOne for enhanced monitoring and management.
- User-friendly output: The script provides easy-to-understand output, making it accessible for IT professionals with varying levels of expertise.
Introduction
Windows activation and licensing are critical components in maintaining the legality and functionality of Windows systems in the IT environment. Ensuring that Windows is properly licensed and activated is not just a legal requirement but also a prerequisite for receiving important updates and support. In this context, PowerShell scripts emerge as powerful tools for IT professionals and Managed Service Providers (MSPs) to automate and streamline the process of checking Windows activation and license status.
Background
The provided PowerShell script is designed to detect the activation and license status of Windows operating systems. It is particularly valuable in environments where multiple machines need to be managed, ensuring that all are compliant with Microsoft’s licensing requirements. This script is a boon for IT professionals and MSPs, allowing them to quickly identify unlicensed or non-activated Windows installations, thereby avoiding potential legal issues and ensuring system integrity.
The script
#Requires -Version 5.1 <# .SYNOPSIS Condition script for detecting activation and license status of Windows. .DESCRIPTION Condition script for detecting activation and license status of Windows. Exit codes: 0 = Activated and Licensed 2 = Unlicensed, but under a grace period 3 = Not Activated and Unlicensed .NOTES Minimum OS Architecture Supported: Windows 10, Windows Server 2016 Release Notes: Renamed script By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://www.ninjaone.com/terms-of-use. Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms. Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party. Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider. Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations. Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks. Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script. EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA). #> [CmdletBinding()] param () begin { # https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/dn502528(v=ws.11) $NotificationReasons = { [PSCustomObject]@{ ErrorCode = "0xC004C001" ErrorMessage = "The activation server determined the specified product key is invalid" ActivationType = "MAK" PossibleCause = "An invalid MAK was entered." TroubleshootingSteps = "Verify that the key is the MAK provided by Microsoft. Contact the Microsoft Activation Call Center to verify that the MAK is valid." } [PSCustomObject]@{ ErrorCode = "0xC004C003" ErrorMessage = "The activation server determined the specified product key has been blocked" ActivationType = "MAK" PossibleCause = "The MAK is blocked on the activation server." TroubleshootingSteps = "Contact the Microsoft Activation Call Center to obtain a new MAK and install/activate the system." } [PSCustomObject]@{ ErrorCode = "0xC004C008" ErrorMessage = "The activation server reported that the product key has exceeded its unlock limit." ActivationType = "KMS" PossibleCause = "The KMS key has exceeded the activation limit." TroubleshootingSteps = "KMS host keys will activate up to 10 times on six different computers. If more activations are necessary, contact the Microsoft Activation Call Center." } [PSCustomObject]@{ ErrorCode = "0xC004C020" ErrorMessage = "The activation server reported that the Multiple Activation Key has exceeded its limit." ActivationType = "MAK" PossibleCause = "The MAK has exceeded the activation limit." TroubleshootingSteps = "MAKs by design have a limited number of activations. Contact the Microsoft Activation Call Center." } [PSCustomObject]@{ ErrorCode = "0xC004C021" ErrorMessage = "The activation server reported that the Multiple Activation Key extension limit has been exceeded." ActivationType = "MAK" PossibleCause = "The MAK has exceeded the activation limit." TroubleshootingSteps = "MAKs by design have a limited number of activations. Contact the Microsoft Activation Call Center." } [PSCustomObject]@{ ErrorCode = "0xC004F009" ErrorMessage = "The Software Protection Service reported that the grace period expired." ActivationType = "MAK" PossibleCause = "The grace period expired before the system was activated. Now, the system is in the Notifications state." TroubleshootingSteps = "See the section User Experience." } [PSCustomObject]@{ ErrorCode = "0xC004F00F" ErrorMessage = "The Software Licensing Server reported that the hardware ID binding is beyond level the of tolerance." ActivationType = "MAK/KMS client/KMS host" PossibleCause = "The hardware has changed or the drivers were updated on the system." TroubleshootingSteps = "MAK: Reactivate the system during the OOT grace period using either online or phone activation. KMS: Restart, or run slmgr.vbs /ato." } [PSCustomObject]@{ ErrorCode = "0xC004F014" ErrorMessage = "The Software Protection Service reported that the product key is not available" ActivationType = "MAK/KMS client" PossibleCause = "No product keys are installed on the system." TroubleshootingSteps = "Install a MAK product key, or install a KMS Setup key found in \\sources\\Product.ini on the installation media." } [PSCustomObject]@{ ErrorCode = "0xC004F02C" ErrorMessage = "The Software Protection Service reported that the format for the offline activation data is incorrect." ActivationType = "MAK/KMS client" PossibleCause = "The system has detected that the data entered during phone activation is not valid." TroubleshootingSteps = "Verify that the CID is correctly entered." } [PSCustomObject]@{ ErrorCode = "0xC004F038" ErrorMessage = "The Software Protection Service reported that the computer could not be activated. The count reported by your Key Management Service (KMS) is insufficient. Please contact your system administrator." ActivationType = "KMS client" PossibleCause = "The count on the KMS host is not high enough. The KMS count must be ≥5 for Windows Server or ≥25 for Windows client." TroubleshootingSteps = "More computers are needed in the KMS pool for KMS clients to activate. Run Slmgr.vbs /dli to get the current count on the KMS host." } [PSCustomObject]@{ ErrorCode = "0xC004F039" ErrorMessage = "The Software Protection Service reported that the computer could not be activated. The Key Management Service (KMS) is not enabled." ActivationType = "KMS client" PossibleCause = "This error occurs when a KMS request is not answered." TroubleshootingSteps = "Troubleshoot the network connection between the KMS host and the client. Make sure that TCP port 1688 (default) is not blocked by a firewall or otherwise filtered." } [PSCustomObject]@{ ErrorCode = "0xC004F041" ErrorMessage = "The Software Licensing Service determined that the Key Management Service (KMS) is not activated. KMS needs to be activated. Please contact system administrator." ActivationType = "KMS client" PossibleCause = "The KMS host is not activated." TroubleshootingSteps = "Activate the KMS host with either online or phone activation." } [PSCustomObject]@{ ErrorCode = "0xC004F042" ErrorMessage = "The Software Protection Service determined that the specified Key Management Service (KMS) cannot be used." ActivationType = "KMS client" PossibleCause = "Mismatch between the KMS client and the KMS host." TroubleshootingSteps = "This error occurs when a KMS client contacts a KMS host that cannot activate the client software. This can be common in mixed environments that contain application and operating system-specific KMS hosts, for example." } [PSCustomObject]@{ ErrorCode = "0xC004F050" ErrorMessage = "The Software Protection Service reported that the product key is invalid." ActivationType = "KMS, KMS client, MAK" PossibleCause = "This can be caused by a typo in the KMS key or by typing in a Beta key on a Released version of the operating system." TroubleshootingSteps = "Install the appropriate KMS key on the corresponding version of Windows. Check the spelling. If the key is being copied and pasted, make sure that em dashes have not been substituted for the dashes in the key." } [PSCustomObject]@{ ErrorCode = "0xC004F051" ErrorMessage = "The Software Protection Service reported that the product key is blocked." ActivationType = "MAK/KMS" PossibleCause = "The product key on the activation server is blocked by Microsoft." TroubleshootingSteps = "Obtain a new MAK/KMS key, install it on the system, and activate." } [PSCustomObject]@{ ErrorCode = "0xC004F074" ErrorMessage = "The Software Protection Service reported that the computer could not be activated. No Key Management Service (KMS) could be contacted. Please see the Application Event Log for additional information." ActivationType = "KMS Client" PossibleCause = "All KMS host systems returned an error." TroubleshootingSteps = "Troubleshoot errors from each event ID 12288 associated with the activation attempt." } [PSCustomObject]@{ ErrorCode = "0xC004F06C" ErrorMessage = "The Software Protection Service reported that the computer could not be activated. The Key Management Service (KMS) determined that the request timestamp is invalid." ActivationType = "KMS client" PossibleCause = "The system time on the client computer is too different from the time on the KMS host." TroubleshootingSteps = "Time sync is important to system and network security for a variety of reasons. Fix this issue by changing the system time on the client to sync with the KMS. Use of a Network Time Protocol (NTP) time source or Active Directory Domain Services for time synchronization is recommended. This issue uses UTP time and is independent of Time Zone selection." } [PSCustomObject]@{ ErrorCode = "0x80070005" ErrorMessage = "Access denied. The requested action requires elevated privileges." ActivationType = "KMS client/MAK/KMS host" PossibleCause = "User Account Control (UAC) prohibits activation processes from running in a non-elevated command prompt." TroubleshootingSteps = "Run slmgr.vbs from an elevated command prompt. Right-click cmd.exe, and then click Run as Administrator." } [PSCustomObject]@{ ErrorCode = "0x8007232A" ErrorMessage = "DNS server failure." ActivationType = "KMS host" PossibleCause = "The system has network or DNS issues." TroubleshootingSteps = "Troubleshoot network and DNS." } [PSCustomObject]@{ ErrorCode = "0x8007232B" ErrorMessage = "DNS name does not exist." ActivationType = "KMS client" PossibleCause = "The KMS client cannot find KMS SRV RRs in DNS. If a KMS host does not exist on the network, a MAK should be installed." TroubleshootingSteps = "Confirm that a KMS host has been installed and DNS publishing is enabled (default). If DNS is unavailable, point the KMS client to the KMS host by using slmgr.vbs /skms \u003ckms_host_name\u003e. Optionally, obtain and install a MAK; then, activate the system. Finally, troubleshoot DNS." } [PSCustomObject]@{ ErrorCode = "0x800706BA" ErrorMessage = "The RPC server is unavailable." ActivationType = "KMS client" PossibleCause = "Firewall settings are not configured on the KMS host, or DNS SRV records are stale." TroubleshootingSteps = "Ensure the Key Management Service firewall exception is enabled on the KMS host computer. Ensure that SRV records point to a valid KMS host. Troubleshoot network connections." } [PSCustomObject]@{ ErrorCode = "0x8007251D" ErrorMessage = "No records found for given DNS query." ActivationType = "KMS client" PossibleCause = "The KMS client cannot find KMS SRV RRs in DNS." TroubleshootingSteps = "Troubleshoot network connections and DNS." } }.Invoke() } process { Write-Host "" $LicenseStatus = Get-CimInstance -ClassName "SoftwareLicensingProduct" -Filter "Name like 'Windows%'" -ErrorAction SilentlyContinue | Where-Object { $_.PartialProductKey } | Select-Object -ExpandProperty LicenseStatus $ActivationNumber = switch ($LicenseStatus) { 0 { 3 } 1 { 0 } 2 { 2 } 3 { 2 } 4 { 3 } 5 { 3 } 6 { 2 } default { 3 } } $Result = cscript.exe C:\Windows\system32\slmgr.vbs -dli $Result | Select-Object -Skip 4 | Out-String | Write-Host $Notification = $Result -split [System.Environment]::NewLine | Where-Object { $_ -match "^Notification Reason: .*" } Write-Host "" if ($Notification) { $NotificationCode = $($($Notification -split ': ')[1] -split '\.')[0] Write-Host "[Error] KMS Activation Error Found." if ($NotificationCode -like "0xC004F200") { Write-Host "Non-Genuine" } elseif ($NotificationCode -like "0xC004F009") { Write-Host "Grace Time Expired" } else { $NotificationReasons | Where-Object { $_.ErrorCode -like "$NotificationCode" } | Out-String | Write-Host } } if ($Result -like "*Eval*") { Write-Host "Evaluation Licensed" exit 5 # Is Activated but is an Evaluation license } exit $ActivationNumber } end { }
Access over 300+ scripts in the NinjaOne Dojo
Detailed breakdown
The script begins by defining a series of potential error codes and messages related to Windows activation. These are encapsulated within a PowerShell custom object, providing detailed information on the nature of the error, the type of activation (e.g., MAK, KMS), and troubleshooting steps.
- It retrieves the license status of Windows products using the Get-CimInstance cmdlet.
- The license status is then evaluated through a switch statement, translating it into an exit code that represents different states of activation and licensing.
- It runs slmgr.vbs -dli, a script that provides detailed information about Windows activation and licensing status.
- The script parses the output of slmgr.vbs to identify any specific activation errors and provides the corresponding troubleshooting information from the earlier defined custom objects.
Potential use cases
Imagine an MSP managing the IT infrastructure for a medium-sized business. They can schedule this script to run periodically across all Windows machines. If a machine is found with a non-activated or unlicensed Windows installation, the script can alert the MSP, enabling them to take corrective action promptly.
Comparisons
Traditional methods of checking Windows activation involve manual checks on each system or using disparate tools, which can be time-consuming and inconsistent. This script offers a centralized, automated approach, ensuring consistency and saving time.
FAQs
- How do I run this script?
- Run the script in PowerShell as an administrator.
- Will this script activate Windows for me?
- No, it only checks the activation status and provides troubleshooting steps.
- Can this script run on all Windows versions?
- It is designed for Windows 10 and Windows Server 2016 and above.
Implications
Incorrectly licensed or non-activated Windows systems can pose significant security risks, as they may not receive critical updates. This script helps maintain compliance and security standards within an IT infrastructure.
Recommendations
- Regularly schedule the script to run across all Windows machines in your network.
- Review and act on the script’s output promptly to maintain compliance.
- Ensure that PowerShell execution policies allow the running of scripts.