Tag Archives: PowerShell

DCM Script – Detect Office Activation Status on Windows 7 and Activate if Unactivated

This one was a lot of fun and by “fun”, I mean a complete pain.

Recently, several of my helpdesk calls have been along the lines of “When I open Word, it says that it needs activating”. As I’d hope most people with more than 20 PCs to manage do, we use a Key Management Services (KMS) Server to activate all of our Windows and Office clients. Windows and Office are supposed to activate themselves either during the build process or very soon afterwards. However, the PCs need to phone back to the KMS server every 180 days to remain activated so either the PC hasn’t activated Office during the build process or its activation ticket has expired and it hasn’t managed to get a new one. Therefore, I needed a way to detect whether Office is activated on a computer and activate it if it wasn’t. Detect a state? Remediate it if it isn’t in a desired state? Hmm, this sounds like something thats perfect for DCM! So I went a-looking, seeing what I could see.

First of all, this post is written for 64 bit machines which are running 32 bit Office. However, if you’re running 64 bit Office or 32 bit Office on 32 bit Windows, it’s just a matter of adjusting the paths for the Office VBS script accordingly.

At first, I hoped that I could use pure PowerShell to fix this. There is a very handy CIM instance called SoftwareLicensingProduct which lists the activation status for the Microsoft products installed on your computer. I thought a simple Powershell command like

Get-CimInstance SoftwareLicensingProduct -Filter "Description LIKE '%KMSCLIENT%'" | select ID, Description, LicenseStatus, Name, GenuineStatus

would give me a nice base to work from. On my Windows 8.1 machine, it does; it lists all of the KMS products on your PC and their activation statuses. However, on Windows 7, that CIM instance only lists the operating system, not Office and unfortunately Windows 7 is what is installed on the vast majority of the computers in my workplace. So that meant going back to the drawing board.

I needed another way to get the activation status for Office. From Office 2010 onwards, there is a VBS script called ospp.vbs. It needs to be run with the cscript interpreter as it’s purely command line rather than GUI driven. There are several switches for it which perform operations like attempting an activation, clearing the activation status, setting the KMS server name and port and displaying the activation status of the various Office products. Running the following command:

cscript "C:\Program Files (x86)\Microsoft Office\Office 15\ospp.vbs" /dstatus

returned the following output on my PC with Office 2013 Pro Plus, Project 2013 Standard and Visio 2013 Pro installed on it:

---Processing--------------------------
---------------------------------------
SKU ID: 427a28d1-d17c-4abf-b717-32c780ba6f07
LICENSE NAME: Office 15, OfficeProjectStdVL_KMS_Client edition
LICENSE DESCRIPTION: Office 15, VOLUME_KMSCLIENT channel
LICENSE STATUS: ---LICENSED---
REMAINING GRACE: 177 days (256304 minute(s) before expiring)
Last 5 characters of installed product key: 8QHTT
Activation Type Configuration: ALL
KMS machine name from DNS: kmsserver.domain:1688
Activation Interval: 120 minutes
Renewal Interval: 10080 minutes
KMS host caching: Enabled
---------------------------------------
SKU ID: b322da9c-a2e2-4058-9e4e-f59a6970bd69
LICENSE NAME: Office 15, OfficeProPlusVL_KMS_Client edition
LICENSE DESCRIPTION: Office 15, VOLUME_KMSCLIENT channel
LICENSE STATUS: ---LICENSED---
REMAINING GRACE: 177 days (256304 minute(s) before expiring)
Last 5 characters of installed product key: GVGXT
Activation Type Configuration: ALL
KMS machine name from DNS: kmsserver.domain:1688
Activation Interval: 120 minutes
Renewal Interval: 10080 minutes
KMS host caching: Enabled
---------------------------------------
SKU ID: e13ac10e-75d0-4aff-a0cd-764982cf541c
LICENSE NAME: Office 15, OfficeVisioProVL_KMS_Client edition
LICENSE DESCRIPTION: Office 15, VOLUME_KMSCLIENT channel
LICENSE STATUS: ---LICENSED---
REMAINING GRACE: 177 days (256304 minute(s) before expiring)
Last 5 characters of installed product key: RM3B3
Activation Type Configuration: ALL
KMS machine name from DNS: kmsserver.domain:1688
Activation Interval: 120 minutes
Renewal Interval: 10080 minutes
KMS host caching: Enabled
---------------------------------------
---------------------------------------
---Exiting-----------------------------

Apart from the KMS Server, that output is verbatim. There is some very useful information in there; the product license, the activation information, the KMS server it’s using to activate, how long the activation has left. It’s great! Unfortunately it’s also a big lump of text which isn’t especially useful by itself.

At this point, I could have just created a package which ran

cscript "C:\Program Files (x86)\Microsoft Office\Office 15\ospp.vbs" /act

and called it a day. It certainly would have worked to an extent but I still wanted to use DCM. Using DCM would have been better because:

  • I can, in theory, set it to detect whether Office needs activating and only run the activation script if it’s not whereas using a package with that command line in it will attempt activation of Office whether it needs activating or not
  • Using a package would be a set-once kind of affair, if Office decides to deactivate itself or fails reactivation after the KMS grace period expires, using a package won’t allow the script to re-run whereas using DCM, I can re-run the detection script every hour, every day, every week, every month or whatever

So I turned back to PowerShell and, eventually, came up with this:

C:\Windows\System32\cscript.exe 'C:\Program Files (x86)\Microsoft Office\Office15\OSPP.VBS' /dstatus | Out-File $env:temp\actstat.txt

$ActivationStatus = $($Things = $(Get-Content $env:temp\actstat.txt -raw) `
                            -replace ":"," =" `
                            -split "---------------------------------------" `
                            -notmatch "---Processing--------------------------" `
                            -notmatch "---Exiting-----------------------------"
                       $Things | ForEach-Object {
                       $Props = ConvertFrom-StringData -StringData ($_ -replace '\n-\s+')
                       New-Object psobject -Property $Props  | Select-Object "SKU ID", "LICENSE NAME", "LICENSE DESCRIPTION", "LICENSE STATUS"
        })

$Var = "Office Activated "
for ($i=0; $i -le $ActivationStatus.Count-2; $i++) {
    if ($ActivationStatus[$i]."LICENSE STATUS" -eq "---LICENSED---") {
        $Var = $Var + "OK "
        }

    else {
        $Var = $Var + "Bad "
        }
        }

If ($Var -like "*Bad*") {

    echo "Office Not Activated"
}
else
{
    echo "Office Activated"
}

That script runs the Office activation VBScript and saves the output to a text file in the user’s TEMP directory. It reads the created text file and dumps the entire lot into a variable called Things (I was experimenting, I couldn’t think of a better name once I had finished and hey, it worked! If it ain’t broke don’t fix it). It converts the text file into a series of PowerShell objects using the series of dashes to separate them, replaces any colons with equals signs and excludes the “Processing” and “Exiting” lines. It uses the ConvertFrom-StringData command to add and populate properties on the objects which is why the colons needed replacing. It then selects the particular properties that I’m interested in. The whole lot gets put into a array called ActivationStatus which I can now use to do what I need to do.

The script creates another object called Var and pre-populates it with a bit of random text. It runs through all but the last object in the ActivationStatus array (If you look at the text file output, you’ll see that the series of dashes appears twice at the end so my little routine creates a blank but not null object at the end of the array) and checks to see if the “LICENSE STATUS” property is equal to ‘— LICENSED —“. If so, it appends “OK ” onto the end of Var, if not it adds “Bad “. Finally, the script looks at Var and sees if the word “Bad” appears in it. If so, it echos back to ConfigMgr that Office is activated or not activated.

The remediation script looks like this:

cscript "C:\Program Files (x86)\Microsoft Office\Office 15\ospp.vbs" /act

Simple, no?

When you’ve created the Detection and Remediation scripts inside ConfigMgr, create a Compliance Rule which looks for a string called “Office Activated”. Then, as always, either create a new baseline and deploy it to a collection or add it to an existing one.

Automating SCVMM Patching

Patching. What a pain in the backside it is. It’s bad enough when you just have to look after one or two machines at home. When you have thousands of the things to look after, it can be horrific.

A recent brace of high level, out of band Microsoft security updates has forced me to take a close look at our patching infrastructure at work and make some improvements. Among those improvements was to look at the patching of our Hyper-V hosts. I’m a little ashamed to admit it but I haven’t really looked at patching our virtual hosts since they were installed back in August 2013. This meant that when I went to install those updates, there were another 130 to install with them. Not so good.

We are using System Center Virtual Machine Manager to manage our virtualisation farms. Part of its functionality is keeping your hosts patched. To do so through the UI is a pretty long winded process, you have to add a WSUS server to the SCVMM infrastructure, synchronise SCVMM with WSUS, add any new updates to a baseline, scan the hosts against that baseline to see if they’re compliant and remediate them if they’re not. It’s simple enough but it’s long winded. Unfortunately there is no way to automate this process through the GUI.

However, thanks to PowerShell it can be scripted! I am using this script to mostley automate the process for me:


Import-Module virtualmachinemanager
Import-Module virtualmachinemanagercore

$anonUsername = "anonymous"
$anonPassword = ConvertTo-SecureString -String "anonymous" -AsPlainText -Force
$anonCredentials = New-Object System.Management.Automation.PSCredential($anonUsername,$anonPassword)
$PSEmailServer = "smtp.server.domain"

Get-VMMServer -ComputerName scvmm.domain.com
Start-SCUpdateServerSynchronization -UpdateServer wsus.server.domain

$2012Updates = $(Get-SCUpdate | Where-Object -FilterScript { `
        $_.Products -like "Windows Server 2012" -and `
        $_.IsSuperseded -eq $false -and `
        $_.CreationDate -gt $((Get-Date).AddMonths(-1)) `
        })

$Baseline = New-SCBaseline -Name "$(Get-Date -format y) Updates"
$AddedUpdateList = @()
$2012Updates | foreach {
        $AddedUpdateList += Get-SCUpdate -ID $_.ID
        }

$scope = Get-SCVMHostGroup -Name "Hyper-V" -ID "8db6c432-7326-429d-af6d-8c93d201ca9f"
Set-SCBaseline -Baseline $baseline -AddAssignmentScope $scope -JobGroup "a0bcf812-b866-474b-a69a-13db8f8ec360" -RunAsynchronously
Set-SCBaseline -Baseline $baseline -RunAsynchronously -AddUpdates $addedUpdateList -JobGroup "a0bcf812-b866-474b-a69a-13db8f8ec360" -StartNow

Start-Sleep -Seconds 10

Get-SCVMHostCluster -Name "cluster1.domain.com" | Start-SCComplianceScan
Get-SCVMHostCluster -Name "cluster2.domain.com" | Start-SCComplianceScan

Send-MailMessage -to "helpdesk@domain.com" `
       -from "scvmm@domain.com" `
       -subject "Time to patch the Hyper-V Farms" `
       -Body "Dear IT Support,

Virtual Machine Manager has downloaded the $(Get-Date -format y) updates for the Windows hosts in the Hyper-V clusters. Please ask a member of the team to check the compliance status of the hosts in SCVMM and remediate any problems that are found.

TTFN,

SCVMM"`
       -priority High `
       -credential $anonCredentials

That script connects to SCVMM, looks for any updates released for Windows Server 2012 in the last month and adds them to a new baseline named after the month. It assigns the new updates to a group called Hyper-V and starts a compliance scan on two clusters. Finally, it emails the helpdesk to let them know that there are new updates to be installed. That’s what the anonymous credentials are in there for, the account that I use to run the script doesn’t have a mailbox so our Exchange server rejects the message when it tries to authenticate using it.

If you’re feeling particularly brave, you can add this to the script:


Get-VMHostCluster -Name ClusterName | Start-SCUpdateRemediation -RemediateAllClusterNodes

That will, in theory, cycle through each of the hosts in your cluster putting them into maintenance mode, migrate the VMs on that host onto another in the cluster, install the updates and reboot them. I say “In theory” because I haven’t managed to get this to work 100% reliably yet.

I then created a scheduled task which runs this script on the second Wednesday of every month.

Anyway, there you have it. Feel free to steal this if you want it but run it at your own risk, I’m not responsible if it does something unfortunate.

%d bloggers like this: