Script to create new local admin account for use with LAPS

I’ve got a bunch of older SOE machines that still had the local Administrator account enabled. As part of implementing Microsoft LAPS, I wanted to disable that account, and use a newly-created ‘LocalAdmin’ account with LAPS.

The account is created with a randomly-generated GUID as the password. The account’s password is going to come under the management of LAPS anyway. Additionally, it would be a terrible idea to hard-code a password into a script that’s stored in Sysvol.

If an account with that name already exists, the script will quit. Some basic events are also logged to the Event Log to indicate what happened.

My first revision of the script used ADSI to create the account and add it to the Administrators group, but I found that my mileage varied with that method. Some computers had the account created, but it wasn’t a member of Administrators.

It’s now set up to use plain “NET USER” and “NET LOCALGROUP” commands. This is an example of what would be executed:
2016-04-04 16_01_06

This script is designed to be set up as a computer Startup Script:

# The name of the account
$accountName = 'LocalAdmin'
$accountFullName = 'Local Administrator'
$accountComment = 'Backup Local Administrator Account'
# Any users listed here will be disabled by this script
$usersToDisable = 'Administrator','Guest'
# Set up some Event Log stuff
$sourceName = "$($MyInvocation.MyCommand.Name).ps1"
New-EventLog LogName Application Source "$sourceName" ErrorAction SilentlyContinue WarningAction SilentlyContinue
# If the account already exists, exit
if ((Get-WmiObject Win32_UserAccount filter "domain = '$Env:COMPUTERNAME' and Name = '$accountName'") -ne $null) {
Write-EventLog LogName Application Source $sourceName EntryType Information EventId 1 Message "$accountName already exists" ErrorAction SilentlyContinue WarningAction SilentlyContinue
# Create the account
cmd.exe /c "net user $accountName `"$([guid]::NewGuid().guid)`" /add /y /comment:`"$accountComment`" /fullname:`"$accountFullName`""
# Add the account to the Administrators group
cmd.exe /c "net localgroup Administrators $accountName /add"
# Disable the specified users
$usersToDisable | Foreach-Object {cmd.exe /c "net user $_ /active:no"}
# Try and write an event to the Event Log
Write-EventLog LogName Application Source $sourceName EntryType Information EventId 2 Message "Created local administrator account: $accountName" ErrorAction SilentlyContinue WarningAction SilentlyContinue

Configure Desired State Configuration (DSC) on CentOS 7

Here’s a quick guide on how to set up DSC on CentOS 7. This requires OMI and DSC for Linux. It’s a bit of a pain to track down the downloads for these, and the OMI one doesn’t play ball when using wget, so I’ve put them on Dropbox for ease of use:

I always use the Minimal installation of CentOS. I started with CentOS-7-x86_64-Minimal-1511.iso

  1. Install CentOS, configure an IP address
  2. Connect in via SSH, or the console, run the following commands:
    1. yum install wget -y
    2. cd /home
    3. wget
    4. wget
    5. tar -zxvf omi1084
    6. tar -zxvf dsc_linux_111
    7. rpm -ivh omi-1.0.8.ssl_100.x64.rpm
    8. systemctl start omid.service
    9. rpm -ivh dsc-1.1.1-70.ssl_100.x64.rpm

To test connectivity from a Windows machine, do the following:

$session = New-CimSession -Credential (Get-Credential) -Authentication Basic -ComputerName <name or IP here> -SessionOption (New-CimSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck -UseSsl)
Get-CimInstance -CimSession $session -ClassName OMI_Identify -Namespace root/omi

You’ll be prompted to enter credentials after that first line. Then, after running Get-CimInstance you should see an output as per below:

2016-02-28 12_36_47

The CentOS machine is now ready to receive configs using DSC. I’ll be blogging more related stuff soon, but there’s a great write-up on the PowerShell Documentation pages that goes into more detail:

Get started with Desired State Configuration (DSC) for Linux

List email addresses for ActiveSync device associations in Office 365

Today I had to get a list of email addresses for iOS users associated with a specific Office 365 tenant.

Using the Get-MobileDevice cmdlet in Exchange/Office 365 PowerShell is handy, but it only shows the user’s name, not their email address:

There are some older solutions around on the web that involve running a Get-Mailbox, and then iterating through each mailbox to get the ActiveSync device information. This seemed like overkill to me, as I only needed a basic list.

I ended up with the following one-liner, which uses Calculated Properties to grab the email address:

Get-MobileDevice -ResultSize Unlimited | Select-Object @{Name='User';Expression={(Get-Mailbox -Identity $_.UserDisplayName) | Select-Object -expand WindowsEmailAddress}},DeviceID,DeviceImei,DeviceOS,DeviceType,DeviceUserAgent,DeviceModel | Export-Csv C:\temp\mobile_devices.csv

This allowed me to open the CSV in Excel and filter down the list until I was left with the information that we were after.

Your mileage may vary using this command, as we’re matching a ‘UserDisplayName’ field on a Microsoft.Exchange.Data.Directory.SystemConfiguration.MobileDevice to the ‘Identity’ field on a Microsoft.Exchange.Data.Directory.Management.Mailbox.

Create a Scheduled Task to keep all Chocolatey Packages up-to-date

Rebuilding your PC is always a drag, even with useful utilities like Ninite.

I recently created a PowerShell DSC script that I can use whenever I need to rebuild my PC. As part of that, I used the cChoco provider to automatically install applications using Chocolatey. I’ll be writing a blog post with more details shortly.

That’s a great way to get the applications installed, but not for keeping them up-to-date. Chocolatey allows you to run ‘choco upgrade all’ manually to do this:

Command prompt window showing the results of a 'choco upgrade all -y' command

Rather than manually create the scheduled task to automate this, I created this short PowerShell script:

# See if choco.exe is available. If not, stop execution
$chocoCmd = Get-Command Name 'choco' ErrorAction SilentlyContinue WarningAction SilentlyContinue | Select-Object ExpandProperty Source
if ($chocoCmd -eq $null) { break }
# Settings for the scheduled task
$taskAction = New-ScheduledTaskAction –Execute $chocoCmd Argument 'upgrade all -y'
$taskTrigger = New-ScheduledTaskTrigger AtStartup
$taskUserPrincipal = New-ScheduledTaskPrincipal UserId 'SYSTEM'
$taskSettings = New-ScheduledTaskSettingsSet Compatibility Win8
# Set up the task, and register it
$task = New-ScheduledTask Action $taskAction Principal $taskUserPrincipal Trigger $taskTrigger Settings $taskSettings
Register-ScheduledTask TaskName 'Run a Choco Upgrade All at Startup' InputObject $task Force

The script will:

  1. Locate the choco.exe binary (It’ll quit if it can’t find it in the path)
  2. Set up a scheduled task that runs said binary at system startup

Note that this script will only work on Windows 8 and newer machines, because it relies on the *-ScheduledTask cmdlets.

Use PowerShell to list all Windows Performance Counters and their numeric IDs

I’ve been looking into using Zabbix again lately, and was delighted to see that they’ve now released a refreshed and improved version 3.0

Whilst I was browsing some of my old Chrome bookmarks for Zabbix, I came across their article about Windows performance counters. They mention that performance counters in Windows have localised names, and that if you’re running systems in several different languages, you need to refer to the counters by their ID, rather than by name, when configuring monitoring.

This can be achieved by looking the list up in the registry, but it’s all just stored as a multi-line string value:


This isn’t ideal if you’re searching for something specific, or you just want a nicely-formatted output. I’ve created a quick PowerShell function that outputs these values, and allows you to use standard PowerShell operations to search and filter.

For example, output to a text file:

Get-PerformanceCounter | Out-File c:\temp\perfcounters.txt

Results in this:

Or you could do something like this:

Get-PerformanceCounter | Where-Object {$_.CounterName -like 'file write*'}

Which results in this:

CounterID CounterName              
--------- -----------              
       12 File Write Operations/sec
       18 File Write Bytes/sec

Or, you could sort them by name:

Get-PerformanceCounter | Sort-Object -Property 'CounterName' | Format-Table -AutoSize

Which would result in the following output

CounterID CounterName                                                   
--------- -----------                                                   
     7024 # Bytes in all Heaps                                          
    10686 # exceptions                                                  
     7014 # GC Handles                                                  
     6986 # Gen 0 Collections                                           
     6988 # Gen 1 Collections                                           
     6990 # Gen 2 Collections                                           
    10688 # images                                                      
     7018 # Induced GC                                                  
     7124 # Link Time Checks                                            
     7086 # of CCWs                                                     
     7108 # of current logical Threads                                  
     7110 # of current physical Threads                                 
     7112 # of current recognized threads                               
     7150 # of Exceps Thrown                                            
     7152 # of Exceps Thrown / sec                                      
     5046 # of failed workflow jobs                                     
     5048 # of failed workflow jobs/sec                                 
     7154 # of Filters / sec                                            
     7156 # of Finallys / sec                                           
     7072 # of IL Bytes Jitted                                          
     7090 # of marshalling                                              
     7070 # of Methods Jitted                                           
     7030 # of Pinned Objects                                           
     5050 # of resumed workflow jobs                                    
     5052 # of resumed workflow jobs/sec                                
     5054 # of running workflow jobs                                    
     5056 # of running workflow jobs / sec                              
     7032 # of Sink Blocks in use          

Here’s the code:

function Get-PerformanceCounter
# Get the Performance Counters from the Registry
$counters = Get-ItemProperty Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\009' Name 'counter' | Select-Object ExpandProperty Counter
# Remove the last line
$counters = $counters | Select-Object SkipLast 1
# Split the string into an array
$counters = $counters.Split([Environment]::NewLine)
$counterList = @()
for ($x = 0; $x -lt $counters.Count; $x++)
if ($x % 2 -eq 0) {
$counterList += New-Object TypeName PSObject Property @{ CounterID = [int]$counters[($x -2)];CounterName = [string]$counters[$x 1] }
$counterList | Sort-Object Property 'CounterID'
# Get performance counters sorted by ID
# Get-PerformanceCounter | Sort-Object -Property 'CounterID' | Format-Table -AutoSize
# Search for specific performance counters by name
# Get-PerformanceCounter | Where-Object {$_.CounterName -like 'file write*'}
# Get performance counters, sorted by name
Get-PerformanceCounter | Sort-Object Property 'CounterName' | Format-Table AutoSize

Random PowerShell: Determine the longest job title in AD

This one’s kind of random, and a real edge case, but it shows how handy a bit of PowerShell knowledge is to quickly dig information out of Active Directory.

As part of a email signature redesign, we’d received a new HTML template from our web designers. Whilst testing out the template, I ran into an issue with text wrapping around if the user had a long job title. I needed to get back to the designers with an example of the longest job title we’d be likely to see in our environment.

Here’s how I discovered the longest job title for users in our domain:

Get-ADUser -Filter * -Properties title | Select-Object -ExpandProperty title -Unique | Sort-Object -Property Length | Select-Object -Last 1

I found that our longest job title was 60 characters long (to figure that out, wrap the entire command in parentheses and append ‘.ToCharArray().Count’)

As a side note, I normally never display code samples that use aliases on my blog, but here’s the command that I actually used. This is to illustrate that you don’t need to type out the long-winded version above just to perform a quick task.

get-aduser -filter * -properties title | select -expand title -unique | sort length | select -last 1

Find the MSI ProductCode for Installed Products using the PowerShell App Deployment Toolkit

A quick way to find out MSI product codes for installed products if you have the PowerShell App Deployment Toolkit (PSADT) lying around is to dot source the toolkit’s AppDeployToolkitMain.ps1, and use the Get-InstalledApplication function.

This saves you digging around the registry, and searches both the 64-bit and 32-bit sections of the registry. Also handy, is the fact that the Name parameter on Get-InstalledApplication can take partial names:


Server Core: Change NIC binding order with nvspbind

My router VM in my home lab is a Server 2012 R2 set up running RRAS, but with the GUI features removed to save resources on the host.

It has two NICs – one “External”, facing my home router, and the “Internal”, facing the lab VMs. The external NIC just gets a DHCP address, and along with that, my ISP’s DNS server. I’d also set the DNS server on the Internal NIC, but it wasn’t taking precedence due to the binding order of the NICs. I needed to have the router VM query the lab DNS server so that it could join the domain.

Normally, in Server with a GUI, it would be easy to change the NIC binding order. Out of the box without the GUI, not so much. There’s a 3rd-party utility called nvspbind that can be used to make the change.

If you had a NIC named “Internal” that you wanted to bump up the order, and you already had nvspbind somewhere, you could just run the following:

C:\temp\nvspbind\nvspbind.exe /++ Internal ms_tcpip

Here’s my script that I can run on future lab machines to download nvspbind and configure the correct NIC binding order, assuming that there are NICs named “Internal” and “External”:

$nicName = 'Internal'
$downloadUri = ''

New-Item -ItemType Directory -Path 'c:\temp' -Force
Invoke-WebRequest -Uri $downloadUri -OutFile 'c:\temp\Microsoft_Nvspbind_package.EXE'
& 'C:\temp\Microsoft_Nvspbind_package.EXE' /T:c:\temp\nvspbind /C /Q
& 'C:\temp\nvspbind\nvspbind.exe' /++ 'Internal' ms_tcpip

Azure: Generate RDP files for multiple VMs

Today I needed to pass on the RDP files for a bunch of Azure VMs to an external developer.

Rather than manually click on the download links in the Azure Portal each time these dev VMs are re-generated, I took the PowerShell route. This was simplified by the fact that all of the VMs in question had a similar Service Name.

Here’s the two lines it took to generate the RDP files, assuming that the Azure Service name for these VMs started with ‘dev-‘, and also assuming that I’m already connected to Azure PowerShell and have the correct subscription selected:

$devVms = Get-AzureVM | Where-Object {$_.ServiceName -like 'dev-*'} 
$devVms | ForEach-Object {Get-AzureRemoteDesktopFile -Name $_.Name -LocalPath "C:\temp\dev_rdp\$($_.Name).rdp" -ServiceName $_.ServiceName}

Office 365/Azure AD: Find all users with an email address that matches a specific string

We have a lot of shared mailboxes in Office 365. Today, I needed to find a subset of those,  change their UPN, and set the FirstName and LastName attributes.

I came across this lengthy PowerShell script that someone had created years ago to find users, but the simplest way to do this is via this one line of PowerShell, once you’ve connected to Azure AD:

Get-MsolUser -MaxResults 1000 | Where-Object {($_.ProxyAddresses -like '*texttomatch*')}

An alternative method, if you’re not connected to Azure PowerShell, but are connected to Exchange Online via PowerShell, is to use Get-Mailbox:

Get-Mailbox -ResultSize 1000 | ? {$_.EmailAddresses -like '*texttomatch*'}