Detect vulnerability in TPM (ADV170012) using ConfigMgr Compliance Settings

Introduction

Coming Patch Tuesday this month, Microsoft revealed a whooping vulnerability in some infineon TPM chips; ADV170012

In the above article, Microsoft gives us some insight on the vulnerability itself, as well as how to detect and counter the vulnerability.

As how to detect the vulnerability, they released a patch which writes an entry to the event log and highlights the vulnerability in TPM.msc.

They also released a Powershell script, which they in turn – unfortunately – don’t go into much details about. They tells us to use PSremoting to query multiple computers and nothing else.

So, how about using ConfigMgr to detect whether our computers are vulnerable or not? Compliance Settings to the rescue!

I rewrote their script to instead return $true or $false, and make it usable to detect compliance or non-compliance.

So, following is my edition of the script, and how to setup the CI in ConfigMgr

Configuration

<#

.DESCRIPTION
    Detect if Infineon TPM is vulnerable to Microsoft ADV170012 through Compliance Settings in ConfigMgr

.NOTES
    FileName:    CIDetectTPM.ps1
    Author:      Martin Bengtsson
    Created:     14-10-2017
   
#>

$IfxManufacturerIdInt = 0x49465800 # 'IFX'
		
		function IsInfineonFirmwareVersionAffected ($FirmwareVersion)
		{
			$FirmwareMajor = $FirmwareVersion[0]
			$FirmwareMinor = $FirmwareVersion[1]
			switch ($FirmwareMajor)
			{
				4 { return $FirmwareMinor -le 33 -or ($FirmwareMinor -ge 40 -and $FirmwareMinor -le 42) }
				5 { return $FirmwareMinor -le 61 }
				6 { return $FirmwareMinor -le 42 }
				7 { return $FirmwareMinor -le 61 }
				133 { return $FirmwareMinor -le 32 }
				default { return $False }
			}
		}
		
		function IsInfineonFirmwareVersionSusceptible ($FirmwareMajor)
		{
			switch ($FirmwareMajor)
			{
				4 { return $True }
				5 { return $True }
				6 { return $True }
				7 { return $True }
				133 { return $True }
				default { return $False }
			}
		}
		
		$Tpm = Get-Tpm
		$ManufacturerIdInt = $Tpm.ManufacturerId
		$FirmwareVersion = $Tpm.ManufacturerVersion -split "\."
				
		if (!$Tpm)
		{
			#No TPM found on this system, so the issue does not apply here."
            Return $True
		}
		else
		{
			if ($ManufacturerIdInt -ne $IfxManufacturerIdInt)
			{
				#This non-Infineon TPM is not affected by the issue."
                Return $True
			}
			else
			{
				if ($FirmwareVersion.Length -lt 2)
				{
					#Could not get TPM firmware version from this TPM."
                    Return $True
				}
				else
				{
					if (IsInfineonFirmwareVersionSusceptible($FirmwareVersion[0]))
					{
						if (IsInfineonFirmwareVersionAffected($FirmwareVersion))
						{
							#This Infineon firmware version {0}.{1} TPM is not safe. Please update your firmware." -f [int]$FirmwareVersion[0], [int]$FirmwareVersion[1])
                            Return $False
						}
						else
						{
							#This Infineon firmware version {0}.{1} TPM is safe." -f [int]$FirmwareVersion[0], [int]$FirmwareVersion[1])
                            Return $True
						}
					}
					else
					{
						#This Infineon firmware version {0}.{1} TPM is safe." -f [int]$FirmwareVersion[0], [int]$FirmwareVersion[1])
                        Return $True
					}
				}
			}
		}
  • Create a new CI. Give it a name and enable it to run on all Windows Desktops and Servers (Custom)

  • At the Specify settings for this OS page, click New

  • In the Create Setting page, select Script and Boolean. Insert my script from above in Edit Script

  • In the Create Rule page, select the newly created CI

  • Add the completed Configuration Item to a Configuration Baseline and deploy to selected collections

Summary

  • Taking a closer look directly on the client on the Configurations tab of the ConfigMgr client, you will either notice a compliant or non-compliant state

  • For a better summary of compliance, I personally like to create collections. Go to the deployment of the Configuration Baseline, and right click. Below is your options to create additional collections

  • The net result is a set of collections which memberships clearly tells the compliance state of the TPM vulnerability

Download my CI and baseline here: https://www.imab.dk/mab/CB_TPMVulnerability_Status.zip

Enjoy 😎

 

Switch Office 365 ProPlus update channel using the Software Center in SCCM (System Center Configuration Manager)

Introduction

Following is a post on how I let (some) of our users decide whether they want to roll on the Current Channel (now called Monthly) or the Deferral Channel (Now called Semi-Annual and Broad) for Office 365 ProPlus.

According to numerous blogs on the www, there are several ways of doing this; modifying registry, GPO, reinstalling Office or to rerun setup.exe from the Office Deployment Tool (ODT).

I’ve chosen to go with the last option, and modify the channel through setup.exe coming from ODT. (I spent some time on GPO and modifying the registry without the expected results. However using setup.exe gives you visibility and a method that yields the results right away)

Configuration

  • Go ahead and download Microsoft Deployment Tool (ODT) if you haven’t already. Link: Download Office 2016 Deployment Tool
  • Create two new .xml files containing following content (one for each channel switch)

Semi-Annual channel (also called Deferred or broad):

<Configuration> 

	<Updates Channel="Deferred" /> 

</Configuration>

Monthly channel (also called current):

<Configuration> 

	<Updates Channel="Monthly" /> 

</Configuration>
  • Put the .xml files in folder next to setup.exe from the Microsoft Deployment Tool (ODT)
  • Create two .bat files containing following content (one for each channel switch)
    • I’m deleting the current regkeys responsible for setting the channel prior to making the actual switch. This is due to some weirdness I’ve been seeing where the values are not properly updated.

SetDeferred.bat:

@echo off
reg delete HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\ClickToRun\Configuration /v CDNBaseUrl /f
reg delete HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\ClickToRun\Configuration /v UpdateChannel /f
"%~dp0setup.exe" /configure "Deferred.xml"
"%CommonProgramFiles%\microsoft shared\ClickToRun\OfficeC2RClient.exe" /update user

SetMonthly.bat:

@echo off
reg delete HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\ClickToRun\Configuration /v CDNBaseUrl /f
reg delete HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\ClickToRun\Configuration /v UpdateChannel /f
"%~dp0setup.exe" /configure "Monthly.xml"
"%CommonProgramFiles%\microsoft shared\ClickToRun\OfficeC2RClient.exe" /update user
  • Put the two .bat files into the same folder and copy the content to your content library used in ConfigMgr (Whereever that may be. This is pretty standard, so I’m not going into details here)
  • Create two new Applications in ConfigMgr with a Deployment Type set to Script Installer. Below the snippets from the Create New Application process.

  • Set the previously created .bat files as the installation program (SetDeferred.bat)

  • Detection rules for the applications will be the corresponding registry keys. The value of the below registry key is changing upon switching channels.

Monthly Channel: http://officecdn.microsoft.com/pr/492350f6-3a01-4f97-b9c0-c7c6ddf67d60

Deferred Channel: http://officecdn.microsoft.com/pr/7ffbc6bf-bc32-4f92-8982-f9dd17fd3114

  • Distribute the content of the newly created applications to your preferred distribution points (groups), and deploy the applications.

Summary

Running the applications will result in either an upgrade or downgrade of Office 365 ProPlus.  This should be followed by a series of windows which will take you through the process for the new channel that you have switched to.

This is something I have deployed internally in the IT department, enabling them to easily switch between the channels.

Enjoy! 😎

References:
https://support.microsoft.com/en-us/help/3185078/how-to-switch-from-deferred-channel-to-current-channel-for-the-office
https://blogs.technet.microsoft.com/odsupport/2017/05/10/how-to-switch-channels-for-office-2016-proplus/

Converting from BIOS to UEFI with Powershell (During OSD using SCCM on Lenovo laptops)

Following my previous post, this is an quick example on how to use my Powershell script to convert from BIOS to UEFI in a bare metal scenario. (Again, only Lenovo laptops is working with this script)

Most of the magic lies within the Task Sequence itself, so I will break it down in pieces:

  • Create a group in your Task Sequence called “Prepare Computer (BIOS)” with the condition _SMSTSBootUEFI not equals true (This will make sure the content of the group only runs if UEFI is not enabled already)

  • Next step, format the disk with following settings (Step: Format and Partition Disk (BIOS))
    • Disk type: Standard (MBR)
    • Partition type: Primary
    • 100% remaining disk
    • File system: NTFS

  • Next step, create a new group called Config Lenovo BIOS with following condition: SELECT * FROM Win32_BIOS WHERE Manufacturer = “Lenovo” (This will make sure the step only runs on a Lenovo computer)

  • Next step, run my Powershell script directly from a package like shown below. The parameter -EnableSecureBoot will also enforce UEFI to be enabled.

  • Next step, format the disk with following settings (Step: Format and Partition Disk (BIOS to UEFI))
    • Disk type: GPT
    • Partition type: Primary
    • Size: 600Mb
    • File System: FAT32
    • Variable: TSUEFIDrive
  • Add another partition:
    • Partition type: Primary
    • Size: 100% of remaining space
    • File system: NTFS
    • Variable: None

  • Next step, one final reboot to the boot image currently assigned to this task sequence. When the task sequence returns from the reboot, the Lenovo BIOS will be set to SecureBoot AND UEFI and Windows will continue installing.

Ultimately, you can have 2 steps to take care of when the computer is coming with either BIOS setting or UEFI, and act accordingly. Se below snippet for inspiration.

Enjoy! 🙂

 

Manage Lenovo BIOS with Powershell (During OSD with SCCM)

Update: Post and script moved to https://www.imab.dk/lenovo-bios-configurator/

I have no idea if this is something that has already been created out there, but I figured I’d do it anyway. Mostly because Powershell, I need the practice and I needed the ability to easily modify the BIOS in our Lenovo environment.

The script is inspired by the original VB script from Lenovo, which roughly does the same as my Powershell script. Lenovos script and documentation can be reached from this link (https://support.lenovo.com/dk/en/solutions/ht100612). However, it’s VB and it gives me shivers, so Powershell to the rescue.

The script is still work in progress, as it doesn’t hold all the abilities as the original Lenovo script, but I prioritized the ability to turn on/off following: Virtualization, SecureBoot, PrebootUSB-C/Thunderbolt and TPM.

You basically just run the script with parameters. Example: LenovoBIOSManagement.ps1 -EnableTPM -EnableSecureBoot -DisableVirtualization -Restart

An example from the use in my recent Windows 10 Task Sequence. The step is run prior to formatting the disk in WinPE. I have a condition on the step, to only run if a Lenovo laptop.

Enable Preboot Thunderbolt is separated as a single step, as it’s currently only the most recent Lenovo laptops which has this ability: T470s, X1 Yoga 2nd generation etc., and therefore has a condition to only run if such model is being deployed.

I’d appreciate any feedback. I’m by no means any Powershell champ, but I’m still learning – and willing to learn 🙂

Thanks!

Update: Post and script moved to https://www.imab.dk/lenovo-bios-configurator/

Configuration Manager collection based on Client Creation Date (Dates in WMI)

I just had a very specific scenario, requiring me to target all new mobile devices (post a specific date) with a different set of compliance settings, than the rest of the devices.

In that case you can separate newly created devices in their own collection, based off their creation date.

The time of when a device is created is stored in the database, and the data can be queried through WMI using WQL.

A date formatted in WMI is using following syntax: yyyymmddHHMMSS.xxxxxx±UUU and can look like this:

20170819071629.492270+120

So, if you want all devices created post July 1 2017, your query have to look like this:

The syntax for dates in WMI in details: https://technet.microsoft.com/en-us/library/ee198928.aspx?f=255&MSPPError=-2147217396

Powershell: Enable virtualization and Credential Guard in an instant (Lenovo laptops)

Windows 10 Credential Guard is currently another hot topic considering cyber security. Credential Guard is a new feature in Windows 10 (Enterprise and Education edition) that helps to protect your credentials on a machine from threats such as pass the hash.

To be able to enable Credential Guard in Windows, you need to have virtualization enabled on the CPU in the BIOS. Virtualization is rarely enabled by default, and as such you will need to enable it manually (F1, enter BIOS, modify the setting) or better yet, find a solution to do so remotely and automatically.

I have created following script in Powershell, that initially enables virtualization in the BIOS (Note: We only use Lenovo laptops, hence this is made for Lenovo laptops only) and then apply the registry-keys to enable Credential Guard. All steps are logged into c:\Windows\EnableCredentialGuard.log

The script can be targeted to the proper Windows 10 versions through SCCM collections (I this example I only target W10 1607 and 1703, as these Windows 10 versions no longer require the Isolated User Mode feature when enabling Credential Guard, as it’s now embedded into the Hypervisor)

When deploying powershell script from SCCM, remember to create the program with a command line like this: powershell.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile -WindowStyle Hidden -File .\CredentialGuard\Enable-VirtualizationCredentialGuard.ps1

<#
.SYNOPSIS
    Enable virtualization in Lenovo Bios and enable Credential Guard in Windows 10
.DESCRIPTION
    This script will only run on Lenovo computers. If run on Lenovo computer, the script will check if virtualization is enabled in BIOS. 
    If not, virtualization will be enabled in the process of enabling CredentialGuard.
    Also appends actions to logfile: EnableCredentialGuard.log
 
.NOTES
    FileName:    Enable-VirtualizationCredentialGuard.ps1
    Author:      Martin Bengtsson
    Created:     19-07-2017
#>

$Logfile = "C:\Windows\EnableCredentialGuard.log"

#Create LogWrite function
Function LogWrite
{
   Param ([string]$Logstring)

   Add-Content $Logfile -Value $Logstring
}

#Get computermanufacturer
$Lenovo = Get-WmiObject Win32_ComputerSystemProduct | Select-Object Vendor

#If not a Lenovo laptop, write to log and exit script
If ($Lenovo.Vendor -ne "Lenovo"){
    
    LogWrite "Not a Lenovo laptop - exiting script"
    Write-Warning -Message "Not a Lenovo laptop - exiting script" ; exit 1
}

Else {
    
    Write-Host -ForegroundColor Yellow "Collecting Lenovo_BiosSetting information" ; LogWrite "Collecting Lenovo_BiosSetting information"
    $VirtEnabled = Get-WmiObject -Class Lenovo_BiosSetting -Namespace root\WMI | Where-Object {$_.CurrentSetting -match "Virtualization*"} | Select-Object CurrentSetting

If ($VirtEnabled.CurrentSetting -eq "VirtualizationTechnology,Disable"){
    
    Write-Host -ForegroundColor Cyan "Virtualization disabled - trying to enable virtualization" ; LogWrite "Virtualization disabled - trying to enable virtualization"
    Try {
        (Get-WmiObject -Class Lenovo_SetBiosSetting -Namespace root\wmi).SetBiosSetting("VirtualizationTechnology,Enable")
        (Get-WmiObject -Class Lenovo_SaveBiosSettings -Namespace root\wmi).SaveBiosSettings()

    }
    Catch {
        Write-Host -ForegroundColor Cyan "An error occured when enabling virtualization in the BIOS" ; LogWrite "An error occured when enabling virtualization in the BIOS" ; exit 1
    }
    cls
    Write-Host -ForegroundColor Cyan "Virtualization Successfully enabled" ; LogWrite "Virtualization Successfully enabled"
    
}

#Add required registry key for Credential Guard
$RegistryKeyPath = "HKLM:\SYSTEM\CurrentControlSet\Control\DeviceGuard"
    If (-not(Test-Path -Path $RegistryKeyPath)) {
        Write-Host -ForegroundColor Yellow "Creating HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceGuard registry key" ; LogWrite "Creating HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceGuard registry key"
        New-Item -Path $RegistryKeyPath -ItemType Directory -Force
    }
    #Add registry key: RequirePlatformSecurityFeatures - 1 for Secure Boot only, 3 for Secure Boot and DMA Protection
    New-ItemProperty -Path $RegistryKeyPath -Name RequirePlatformSecurityFeatures -PropertyType DWORD -Value 1
    Write-Host -ForegroundColor Yellow "Successfully added RequirePlatformSecurityFeatures regkey" ; LogWrite "Successfully added RequirePlatformSecurityFeatures regkey"
    
    #Add registry key: EnableVirtualizationBasedSecurity - 1 for Enabled, 0 for Disabled
    New-ItemProperty -Path $RegistryKeyPath -Name EnableVirtualizationBasedSecurity -PropertyType DWORD -Value 1
    Write-Host -ForegroundColor Yellow "Successfully added EnableVirtualizationBasedSecurity regkey" ; LogWrite "Successfully added EnableVirtualizationBasedSecurity regkey"
    
    #Add registry key: LsaCfgFlags - 1 enables Credential Guard with UEFI lock, 2 enables Credential Guard without lock, 0 for Disabled
    New-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Control\Lsa -Name LsaCfgFlags -PropertyType DWORD -Value 2
    Write-Host -ForegroundColor Yellow "Successfully added LsaCfgFlags regkey" ; LogWrite "Successfully added LsaCfgFlags regkey"
    
    Write-Host -ForegroundColor Yellow "Successfully enabled Credential Guard - please reboot the computer" ; LogWrite "Successfully enabled Credential Guard - please reboot the computer"
    
}   

Snip of the logfile when everything succeeds:

Powershell: Users and passwords about to expire

So, it’s that time of year; people heading back and forth on vacation and meanwhile their Active Directory password expires. People tend to miss the default notification popping up in Windows and have done so since forever.

So how about getting an annoying email every day, 7 days prior to the actual expiration? The main goal here is to avoid having users ending up with expired password, and thus causing troubles for the user and generating calls to helpdesk.

Here’s how I do using Powershell: (Some of the body text is in danish because lazy, and my syntax highlighter is on vacation too. Just copy/paste the code directly into your PS ISE, save the script and schedule it to run through Task Scheduler and you’re set. 🙂

<#
.DESCRIPTION
    Loops through all AD users in specified OUs, and check for password expiry date. If is about to expire (7 days) send the user an email about the password is about to expire

.AUTHOR
    Martin Bengtsson - imab.dk
#>

try {
    Import-Module ActiveDirectory -ErrorAction Stop
} 
catch {
    Write-Error "Active Directory module failed to Import. Terminating the script."
    exit(1)
}

#Email SMTP variables
$AnonUsername = "anonymous"
$AnonPassword = ConvertTo-SecureString -String "anonymous" -AsPlainText -Force
$AnonCredentials = New-Object System.Management.Automation.PSCredential($AnonUsername,$AnonPassword)
$SMTPServer = "smtp.yoursmtp.com”
$From = "Helpdesk <hlp@yourdomain.com>"
$Subject = "Password will expire soon!"

#What OUs to look for users
$OUs = "CN=Schema,CN=Configuration,DC=EUROPE,DC=TEST,DC=CONTOSO,DC=COMe"

#Get todays date and format as string dd/MM/yyyy
$Today = (Get-Date).ToString("dd/MM/yyyy")

#Find all the users and select the objects we need
$Users = ForEach ($OU in $OUs) {Get-ADUser -SearchBase $OU -Filter {Enabled -eq $True -and PasswordNeverExpires -eq $False} –Properties "DisplayName", "Mail", "msDS-UserPasswordExpiryTimeComputed" | Select-Object -Property "Displayname","mail",@{Name="ExpiryDate";Expression={[datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed")}} | % {

#Store selected objects into variables
$Name = $_.Displayname
$To = $_.Mail
$ExpiryDate = $_.ExpiryDate

#Format the date to same format as used in $Today
$ExpiryDate = $ExpiryDate.ToString("dd/MM/yyyy")

#Count the days intil expiration
$DateDiff = New-TimeSpan -Start $Today -End $ExpiryDate

#If less than or equal to 7 days until expiration, create and send HTML email
If ($DateDiff.Days -le 7 -and $DateDiff.Days -ge 0){

    $Body = "
    <html>
    <head>
    <style type='text/css'>
    h1 {
    color: #f07f13;
    font-family: verdana;
    font-size: 20px;
    }

    h2 {
    color: ##002933;
    font-family: verdana;
    font-size: 15px;
    }

    body {
    color: #002933;
    font-family: verdana;
    font-size: 13px;
    }
    </style>
    </head>
    <h1>Dit kodeord udløber snart!</h1>
    <body>
    Kære $Name<br/><br/>
    Dit kodeord udløber om <b>$($DateDiff.Days)</b> dage og har derfor endeligt udløb <b>$ExpiryDate</b>.<br/><br/>
    Husk at skifte kodeord ved CTRL+ALT+DEL, og vælg 'Change a Password'.<br/><br/>
    Skifter du ikke kodeord i tide, så vil du opleve at din iPhone ikke kan modtage/sende e-mails, og at applikationer ikke virker som tiltænkt.<br/><br/>
    Mvh Helpdesk
    </body>
    </html>
    "
    #Override to for testing purposes
    $To = "mab@imab.dk"
    
    #Send the mail message
    Send-MailMessage -To $To -Bcc mab@kromannreumert.com -From $From -Subject $Subject -Body $Body -SmtpServer $SMTPServer -BodyAsHtml -Credential $AnonCredentials -Encoding Unicode
    
    Write-Host -ForegroundColor Cyan "$Name --- $To --- $ExpiryDate"
    #Pause for testing purposes
    Pause
    }
}
}

Preview of the email being sent. Also in danish because lazy.

Powershell: Monitor LAPS

LAPS is Microsoft’s “Local Administrator Password Solution” and is a hot topic when talking about cyber security and what measures to take, when fighting the cyber criminals. Read more about LAPS here.

This is just something short and sweet, and a very simple powershell script to monitor and read all computer objects in specified OUs in Active Directory, read the relevant attributes of the object, and if LAPS attributes are empty (hence no LAPS active), then list the objects in a list and send it as an email.

You can run the script on a schedule using Task Scheduler, and this way monitor which computers in your Active Directory that’s missing LAPS.

<#
.DESCRIPTION
This scripts runs through all computer objects in specified OUs. If ms-Mcs-AdmPwdExpirationTime is empty, return to list and send email containing the list.

.AUTHOR
Martin Bengtsson
#>

try {
    Import-Module ActiveDirectory -ErrorAction Stop -Verbose:$false
} catch {
    Write-Error "Active Directory module failed to Import. Terminating the script. More details : $_"
    exit(1)
}

#Email SMTP variables
$AnonUsername = "anonymous"
$AnonPassword = ConvertTo-SecureString -String "anonymous" -AsPlainText -Force
$AnonCredentials = New-Object System.Management.Automation.PSCredential($AnonUsername,$AnonPassword)
$SMTPServer = "INSERT YOUR SMTP SERVER”
$To = "INSERT RECIPIENTS"
$From = "Active Directory Monitor <SENDER@SENDER.COM>"
$Subject = "Computers (Servers) Missing LAPS Password"

$OUs = "CN=Schema,CN=Configuration,DC=EUROPE,DC=TEST,DC=CONTOSO,DC=COM"

#Loop through all computers in specified OUs. Also filtering on OU, OS and LAPS password already set
$Computers = ForEach ($OU in $OUs) {
Get-ADComputer -SearchBase $OU -SearchScope 'Subtree' -Filter * -Properties * | Where-Object {($_.DistinguishedName -notlike "*OU=Inactive,OU=Servers*") -AND ($_.'ms-Mcs-AdmPwdExpirationTime' -eq $null) -AND ($_.OperatingSystem -like "Windows Server*")} | Select-Object Name, ms-Mcs-AdmPwdExpirationTime, OperatingSystem
}

$Count = $Computers.Count
Write-Host -ForegroundColor Yellow "Count is" $Count
$Body = "<b>Following computers has no LAPS-password stored in Active Directory and are therefore not protected by LAPS: (Count: $Count)</b><br>"

#Add each computer found into body-variable
ForEach ($Computer in $Computers){

    $Body += "<br>" + $Computer.Name
}
#If no computers found, replace body
If ($Computers -eq $null){
    $Body = "No servers are missing LAPS - congratulation"
    Write-Host -ForegroundColor Yellow "No servers are missing LAPS"
}
#Sends the email
Send-MailMessage -To $To -From $From -Subject $Subject -Body $Body -smtpServer $SMTPServer -BodyAsHtml -Credential $AnonCredentials

Preview of the email being sent:

Dynamic Stamps in Adobe Acrobat

This is a tad offtopic, but it took me a while to figure out how to make a dynamic stamp in Adobe Acrobat, as when inserted, prompts the user for input and automatically puts the input on top of the stamp. So here goes: (this requires Adobe Acrobat Pro or the ability to make/edit forms in pdf)

  1. First off, open Adobe Acrobat and go to Comment -> Annotations -> Stamp drop down -> Custom Stamps -> Create Custom Stamp (This is the easy part, so I only touch this briefly)
  2. Browse to your file containing the stamp (I have stamps made in the pdf format. For this I made the stamp in Illustrator)
  3. Give it a name and a category and click OK

With above in place, you now have a new stamp-file located at AppData\Roaming\Adobe\Acrobat\11.0\Stamps. It’s given a random generic name like “PSrfwCzHqxg6fYZmnjYV0D.pdf”.  So far, so good.

Now go ahead and open and edit this file in Adobe Acrobat Pro and:

  1. Select Tools -> Forms -> Edit
  2. Go to Tasks -> Add New Field -> Button and insert the button somewhere in the blank page
  3. Right Click the new button and select Properties
  4. Go to the Action page, and select Run a JavaScript in the Select Action option.
  5. Click Add and paste following two lines of code into the window and click OK and Close
var aTemplates = this.templates;
if (aTemplates) app.alert(aTemplates.join("\r"));

JavaScriptButtonProperties

Now click the new button with the JavaScript action you just made, and take notice of the template IDs in the popup:

javaScriptWarning

#9a6csAl0hXSlWeY-OYTDiD
#WZtYwuwFlm9eAFnYXvCOGA
#y2fXxVRn8AcGrHnfA2BJdD

Above IDs will be used when creating the text field on the stamp. The text field created in the following steps, is where the input from the prompt goes.

  1. Tasks -> Add New Field -> Text Field
  2. Right Click the new text field and select Properties
  3. Go to the Calculate page and insert a Custom calculation script
  4. Insert below script and make sure to your template IDs from above is correct
  5. Place the text field where desired and save the stamp file
var cAsk = "Enter Appendix Number" ;
var cTitle = "Appendix Number:  ";
if(event.source.forReal && (event.source.stampName == "#9a6csAl0hXSlWeY-OYTDiD"))
{
  var cMsg = app.response(cAsk, cTitle);
  event.value = cMsg;
  event.source.source.info.exhibit = cMsg;
}

Now, when the stamp is inserted from Adobe Reader / Adobe Acrobat following window pops up and the net result is my stamp with my input on top of it.

InsertStamp Stamp

Bulk assigning O365 licenses, and then some… using Powershell

Managing our O365 licenses got me an idea to write one of my first Powershell scripts.

The script is tailored to our environment, but can be altered to fit any needs without much hassle. The script looks for users in specified OUs and compare them to what users in O365 that are assigned a license. All users in the specified OU are being assigned the specified license. If a license is assigned to a user, who does not exist in the specified OUs, the license is automatically removed. This way I’m always on top of who is using our licenses.

The script does the following for you in details:

  • (#2) Connects to O365 through Powershell (pre-req for that can be seen here: https://technet.microsoft.com/en-us/library/dn975125.aspx)
  • (#3) Reads what license you want to assign your users in the process. Change this to fit your needs and replace tenantname with your O365 tenant.
  • (#4) Reads what conditions you have for filtering what O365 users that needs a license. I’m excluding my Office 365 Admin and a few others, as I don’t wanna mess with the license for those users.
  • (#5) Reads the OUs containing user who needs a O365 license. You can specify several OUs if needed.
  • (#6) Assign the location and license for each user found in OUs. You can filter additionally in this step if needed.
  • (#7) Remove the O365 license, if user is not found in specified OUs. Change this to fit your needs and replace tenantname with your O365 tenant.
#_1_Imports relevant modules.
Import-Module ActiveDirectory
Import-Module MSOnline

#_2_Prompt for credentials used to log into O365.
$usercredentials = Get-Credential
Connect-MsolService -Credential $usercredentials

#_3_What license are we assigning the users.
$O365License = "tenantname:ENTERPRISEPACK"

#_4_Conditions for O365 users. Excluding certain accounts
$conditions = {$_.isLicensed -eq "TRUE" -AND $_.DisplayName -ne "Office 365 Admin" -AND $_.DisplayName -ne "Expectacademy" -AND $_.DisplayName -ne "London Printer"}

#_5_What OUs are we searching in.
$OUs = "CN=Schema,CN=Configuration,DC=EUROPE,DC=TEST,DC=CONTOSO,DC=COM","CN=Schema,CN=Configuration,DC=EUROPE,DC=TEST,DC=CONTOSO,DC=COM"

#_6_Search the OUs and return to variable. For each emailaddress found, assign to KR O365 license
$out = ForEach ($OU in $OUs) {get-aduser -SearchBase $OU -SearchScope 'Subtree' -Properties '*' -filter {(Description -ne 'Do not delete DMSForLegal') -AND (Enabled -eq $true)} | Select-Object UserPrincipalName}
$O365Users = Get-MsolUser -all | Where-Object $conditions | Select-Object UserPrincipalName
ForEach ($user in $out)
{
    $upn=$($user.UserPrincipalName)
    Set-MsolUser -UserPrincipalName $upn -UsageLocation DK
    Write-Host "Setting location to DK for $($user.UserPrincipalName)"

    Set-MsolUserLicense -UserPrincipalName $upn -AddLicenses $O365License -erroraction 'silentlycontinue'
    Write-Host "Assigning $($O365License) license for $($user.UserPrincipalName)"
}
#_7_For each user assigned to an O365 license, check if user exist in OUs. If not, remove the license. 
ForEach ($user in $O365Users)
{
    If (-Not($out -match $user))
    {
       Write-Host "Removing $($user.UserPrincipalName) from Office 365"
       Set-MsolUserLicense -UserPrincipalName $($user.UserPrincipalName) -RemoveLicenses "tenantname:ENTERPRISEPACK"
    }

}