This is a place for Systems Administrators and IT Professionals to find and share ideas, solutions and templates. If you have something that helps you solve a problem, chances are it will help someone else too. So pay it forward and send an email to TheAgreeableCow at gmail. Full mudos to you!

Monday 8 September 2014

Set a Desktop Wallpaper using PowerShell

This script will apply a desktop wallpaper from a variety of sources and optionally overlay some text, using PowerShell.

The wallpaper sources include:
  • A solid colour (the "no wallpaper" option)
  • A specific or random picture from a directory
  • A Google Image search

The Text Overlay feature provides optional BGInfo style text directly onto the wallpaper image with control over the content, font, size, colour and position.

The script can be run manually, at logon or even repeatedly via a scheduled task to update the wallpaper regularly.

Wallpaper example showing the text overlay


The Shell syntax is very straight forward
        Set-Wallpaper [Source] [Selection] 
    MyPics Examples
        .\Set-Wallpaper.ps1 MyPics *
        .\Set-Wallpaper.ps1 MyPics coolpic.jpg
    Web Example
        .\Set-Wallpaper.ps1 Web 'Ayers Rock'

    Colour Example
        .\Set-Wallpaper.ps1 Colour Blue

Please note,  Powershell v3 or later is required due to the invoke-webrequest cmdlet in the Web module.

Setup Options

All of the options in the script can be set via the Wallpaper Variables section.

# MyPics Options
[STRING]$PicturesPath = [environment]::getfolderpath("MyPictures")+"\wallpaper"
[BOOLEAN]$ResizeMyPics = $False  

# Web Options
[INT]$MaxResults = 10
[INT]$DaysBetweenSearches = 7
[BOOLEAN]$ResizeWebPics = $True
[STRING]$WebProxyServer = ""

# Text Overlay Options
[BOOLEAN]$TextOverlay = $True   
[STRING]$TextColour = "White"
[STRING]$FontName = "Arial"
[INT]$FontSize = 14
[BOOLEAN]$ApplyHeader = $True
[STRING]$TextAlign = "Right"
[STRING]$Position = "High"     

# Wallpaper Style Options
[STRING]$Style = "Fit"         

# Available Colours
$Grey = @(192,192,192)
$Black = @(0,0,0)
$White = @(255,255,255)
$Red = @(220,20,60)
$Green = @(0,128,0)
$Yellow = @(255,255,0)
$Blue = @(0,0,255)
$CornflourBlue = @(100,149,237)

My Pictures Wallaper

The 'MyPics' option will source pictures from a nominated folder ('My Pictures\Wallpaper' by default). You can either choose a specific picture or a wildcard *. Using the wildcard with a scheduled task allows the wallpaper to change regularly. 

Pictures can optionally be re-sized proportionately to match the screen resolution.

Google Images Wallpaper

The 'Web' option will perform a Google image search based on your search term and automatically download a number of high resolution pictures. These are then randomly chosen as the desktop wallpaper. Again the script can be run again manually or via a scheduled task to rotate between the downloaded pictures. 

To avoid repeated downloads each time the script runs with the same search term, simply adjust the $DaysBetweenSearches variable. By default, the script will only repeat a search after a week.

As the images can vary greatly in size, using the re-size variable is recommended here as it will keep the image (and any text overlays) consistent.

Typical download results using the syntax:   set-wallpaper Web 'mountains'

Colour Wallpaper

The solid colour wallpaper is the most straight forward and is essentially a "no wallpaper" option. The result is a plain background as the script simply draws a rectangle in the colour of your choice, at the same resolution of the screen. Extra colours can be added by updating a new variable with the relevant RGB values.

Text Overlay

Mudos for the text overlay proof of concept found on this post, which combines text and your chosen desktop. The text can be anything you like, including dynamic data sourced through powershell and wmi queries such as the computer name, OS or boot time.

There are a number of variables that control the font and where about's on the image the text is placed.

Example Text Overlay on a plain colour wallpaper


There are a number of included functions that tie all of this together. 
  • Get-MyImages chooses pictures for the 'MyPics' option
  • Set-WebProxy allows the Google search connection through a web proxy
  • Get-GoogleImages downloads and chooses pictures for the 'Web' option
  • Set-ImageSize optionally re-sizes pictures proportionately to match screen resolution
  • New-Wallpaper creates the actual bitmap (colour or picture) and combines the text overlay
  • Update-Wallpaper applies the actual wallpaper to your desktop
  • Set-Wallpaper validates the pipeline input and initiates all of the other sub-functions as required

Here's the full script as posted on Git Hub (select the 'Raw' option at the bottom to copy/paste)

         (oo)  ok
   /------\/  /
  / |    ||
 *  /\---/\
    ^^   ^^

Sunday 10 August 2014

Password and Phrase Generator (using PowerShell GUI)

Creating a good password is not as easy as it used to be. Regular English words as passwords are a terrible idea and simply adding a few numbers or characters doesn't help much. The trick to creating a good password is finding the right balance between length and complexity. Here's an article on Stanford University's password policy, which is a great example of getting that mix right.

There are a bunch of password generators out there and even some phrase generators. The TAC Password and Phrase Generator will create both.

Creating Passwords

As soon as the script is run, a password is generated, based on the default settings. Simply increase or decrease the character count and select the complexity as required.

Random passwords of varying complexity from the ASCII table of acceptable password characters

Creating Phrases

Select the 'Words' radio button and increase or decrease the word count as required. By default, words are sourced from a random page on Reddit. You can of course experiment with any sub-reddit you like to theme your phrase.

Phrases from random words selected in real time from posts on Reddit.

You might have heard about the xkcd Password Generator, which is a great insight into the use of password phrases (in this case based on a small static list of common words making them easy to remember, but also easier to crack).

So why words from Reddit? Because it contains a vast and dynamic pool of words that represent the varied nature of the site itself. Once a pool of words has been randomly selected, it is then filtered to remove short words ( anything less than 4 characters), duplicates and common words.

Common Words Filter
Using ongoing samples of words from Reddit, a collection of common words is maintained and used as an exclusion list when generating phrases.

TIP! Download the latest version of the ExcludedCommonWords.txt
and save it in the same location as your PowerShell script.

The graph below shows a typical "Count of Unique Words" distribution. In this example, the common words account for around 40% of Total Words in a pool. However, as you can see they only account for a very small percentage of Unique Words. This means that phrases will be generated from uncommon words, making them harder to crack.

500 common words are automatically excluded from generated phrases


Every time you hit 'Generate' another password is created. Each time it is copied to your clipboard for easy pasting into another application. Use the Mask option to hide the password on screen. The Export feature is useful for bulk transfer of passwords to another application. Please do NOT keep saved passwords in a text file for any period of time!

Please note that this tool simply provides random passwords and phrases in a novel manner. Be sensible with the passwords you choose and use them at your own risk. I am not responsible for anything that happens as a result of your password choice. 

Here's the script to the latest version (v1.3).There may seem like a lot of code here, but most of it is for the generation of the form.

         (oo)  ok
   /------\/  /
  / |    ||
 *  /\---/\
    ^^   ^^

Tuesday 22 July 2014

Sysadmin Modular Report for SSL Certificates (now with check for key size of 1024 bits)

The SysAdmin Modular Reporting framework provides a consistent, flexible data collection and reporting tool with 'traffic light' style alerts for your environment. Written in Powershell using an easy to follow style, the framework collates any number of user generated plugins (function scripts), into a single report for any Windows system supporting Powershell.

Learn about the framework in the new Quick Start Guide (pdf).

Getting started
  1. Download modules from GitHub links below*
  2. Save to a server with Active Directory Powershell Tools
  3. Customise Global_Variables.ps1 as required (variables apply to all modules)
  4. Customise Module_Variables.ps1 as required (variables apply to this module)
  5. Review the plugins (reorder, remove, update thresholds etc)
  6. Run the report  Get-SAMReport Certificates [Email/OnScreen]
Review the scripts on GitHub
*I encourage you to review and understand any script downloaded from the internet. Also ensure to "unblock" each .ps1 files (Right click | Properties | Unblock), to avoid the [D] Do not run [R] Run once [S] Suspend security prompts.


SAMReports can provide a very detailed look into the health of your environment. You can view the relevant data that has been gathered and quickly see any Warnings or Alerts based on your thresholds. The overall title of the report will reflect the worst result, so for example if there were 6 sections and only one showed a Warning, the report title will be coloured as a Warning.

The result is a rich report with clear status indicators giving you an instant overview and the details to back it up.

      Picture 1. Sample Report showing plugins for the stores in a typical certificate chain


The independent plugin system is very flexible and provides an easy way to only report on information that you need. The template style provides a consistent output, but also makes it easy to adapt or add new plugins. The warnings or alerts are based on a failed test or data falling outside of thresholds that you can define.

Each plugin can generate four types of output:
  • Results Text (html formatted)
  • Results Data (html formatted table)
  • Results Status (Alert, Warning, Good colour codes)
  • A File (either saved to the \output folder or included as an email attachment)

About the Certificates Module

UPDATE: The report now includes a check for a key length that is less than 2048 bits. 

The report will list details for all certificates in the relevant stores, for all of your servers. There is even an option to log and remove any expired certificates.

This is a list of the current plugins for the Certificates module, which cover the certificate stores for a typical certificate chain:
  • 00 Module Variables.ps1   (loads AD snap-in, sets server scope, log file location etc)
  • 01 List Personal Certificates.ps1
  • 02 List Intermediate Certificates.ps1
  • 03 List 3rd Party Root Certificates.ps1
  • 04 List Trusted Root Certificates.ps1
There are a number of variables you can customise to change the scope of what is shown in the report, such as:

#Reporting variables
$MaxDays = 1095
$WarnDays = 90
$AlertDays = 30
$WarnKeySize = 2048

#Certificate Store Properties
$StoreLocation = "LocalMachine" #"LocalMachine","CurrentUser"
$StoreName = "My"   #"My","CA","AuthRoot","Root"
$OpenFlag = "ReadWrite"  #"ReadOnly","ReadWrite"

#Purge Variable
$PurgeExpired = $False          #$True or $False
$PurgeDays = -90

This is the core of each script, to show how it collects info from each store and builds it into the main report.

#Create an Array and run query
$ResultsData = @()
foreach ($Server in $Servers) {
    if (Test-Connection -computername $Server -count 1 -quiet){
        $stores = New-Object System.Security.Cryptography.X509Certificates.X509Store("\\$Server\$StoreName",$StoreLocation)
        $Certificates = $stores.Certificates | Select FriendlyName, serialNumber, Issuer, Subject, PublicKey, @{Label="Expires";Expression={($_.NotAfter)}}, @{Label="Days";Expression={($_.NotAfter - (Get-Date)).Days}}
        Add-content -path $Logfile -value "Server: $Server"
        Add-content -path $Logfile -value "Store: $StoreLocation\$StoreName"
        Add-content -path $Logfile -value " "

        foreach ($Certificate in $Certificates) {
            #Build Report
            if ($Certificate.Issuer -ne $null -and $Certificate.days -lt $MaxDays){
                $obj = New-Object PSobject
                $obj | Add-Member -MemberType NoteProperty -name "Server" -value $Server
                $obj | Add-Member -MemberType NoteProperty -name "Name" -value $Certificate.FriendlyName
                $obj | Add-Member -MemberType NoteProperty -name "Issuer" -value $Certificate.Issuer
                $obj | Add-Member -MemberType NoteProperty -name "Subject" -value $Certificate.Subject
                $obj | Add-Member -MemberType NoteProperty -name "Key Size" -value $Certificate.PublicKey.key.KeySize
                $obj | Add-Member -MemberType NoteProperty -name "Expires" -value $Certificate.Expires
                $obj | Add-Member -MemberType NoteProperty -name "Days" -value $Certificate.Days
                $ResultsData += $obj

                # Update Text and Alert count based on your criteria
                $Name = $Certificate.FriendlyName
                $Days = $Certificate.Days
                $Size = $Certificate.PublicKey.key.KeySize

                if ($Days -lt 0){
                    $AlertText += "!RED!Alert: Certificate $Name on $Server has expired "
                    $AlertCount += $AlertCount.count + 1            
                elseif ($Days -lt $AlertDays){
                    $AlertText += "!RED!Alert: Certificate $Name on $Server is expiring in $Days days"
                    $AlertCount += $AlertCount.count + 1            
                elseif ($Days -lt $WarnDays){
                    $WarningText += "!ORANGE!Warning: Certificate $Name on $Server is expiring in $Days days"
                    $WarningCount += $WarningCount.count + 1        
                if ($Size -lt $WarnKeySize){
                    $WarningText += "!ORANGE!Warning: Certificate $Name on $Server does not meet minimum key size of $WarnKeySize"
                    $WarningCount += $WarningCount.count + 1            
            #Log and Purge Old Certs
            If ($PurgeExpired -eq $True){
                $Name = $Certificate.FriendlyName
                $Issuer = $Certificate.Issuer
                $Subject = $Certificate.Subject
                $Expired = $Certificate.Expires
                $Days = $Certificate.Days
                $SerialNumber = $Certificate.serialNumber
                if ($Certificate.Issuer -ne $null -and $Certificate.days -lt $PurgeDays){
                    Add-content -path $Logfile -value "Name: $Name"
                    Add-content -path $Logfile -value "Issuer: $Issuer"
                    Add-content -path $Logfile -value "Subject: $Subject"
                    Add-content -path $Logfile -value "Expired: $Expired"
                    Add-content -path $Logfile -value "Days: $Days"
                    Add-content -path $Logfile -value " "
                    $PurgeCert = $stores.Certificates.Find("FindBySerialNumber",$SerialNumber,$FALSE)[0]
                    $ExpiredCount += $ExpiredCount.count + 1
    Add-content -path $Logfile -value "$Server Completed (Purge = $PurgeExpired). $ExpiredCount expired certificates deleted."
    Add-content -path $Logfile -value "-------------------------------------------------------------------"
    Add-content -path $Logfile -value " "
    $ExpiredCount = 0

More Info

This is a community driven project if you have any suggestion or module scripts you have created, I would love to include them here - with full mudos to you of course.

See the main SysAdmin Modular Reports page for more details, including working with Scheduled Tasks and downloads for other modules.

         (oo)  ok
   /------\/  /
  / |    ||
 *  /\---/\
    ^^   ^^

Saturday 1 February 2014

Deploying and Customising Windows 8.1 using SCCM, Group Policy and Powershell

It was a bold direction Microsoft took with the interface changes in Windows 8. One that has has caused much discussion in the IT ranks. The changes in Windows 8.1 come a long way to resolve the technical limitations many people found with the original release. I don't want to delve into this argument as there are better forums for doing so. Suffice to say that some companies, like mine, are pushing ahead with Windows 8.1 and this article aims to capture some of the techniques, tips and tricks we used to do so.

Any System Administrator worth their salt will know what will fly in their company and what won't. To ease the impact of change on our staff, I like to 'tick-tock' between Operating System and Core Application upgrades, when I do a new Standard Operating Environment (SOE). I had just finished a 'tock' cycle which was focusing on new core applications such as Office, Acrobat, Lync as well as upgrades to our specific Practice and Document Management software. So this environment upgrade was only going to focus on a change to the OS and more specifically, just for laptops and tablets where I feel that Windows 8.1 truly shines.

Systems Center Configuration Manager (SCCM) Task Sequence

I'll have to assume that my audience is somewhat proficient with SCCM, so I will just focus on some of the more specific techniques used with this SOE release. As a minimum, you'll need to ensure that your're pretty up to date with your SCCM version, ADK and patches.

Our target devices were all Dell and included a Venue Pro 11 tablet as well as Latitude 12 and Latitude 14 laptops. It's well worth investing some time getting your driver libraries sorted, so if you're a Dell shop head on over to their Enterprise Client Wiki.

Here's a high level overview of the current task sequence we're using. Where possible, I avoid the use of a "Golden Image" and aim for a complete and flexible build from the original Windows 8.1 ISO.

Copy SXS source files to local computer

Having some OS source files available on your local hard disk makes updates, such as .NET 3.5 much easier. We simply created a Package which was the ISO's SXS directory. Then run the following command line sequence, linked to that package.

xcopy ".\*.*" "C:\Windows\Support\" /D /E /C /I /Q /H /R /Y /S
DISM /Online /Enable-Feature /FeatureName:NetFx3 /All /LimitAccess /Source:c:\Windows\Support\sxs

Removing default apps

There are some apps that you just cannot remove from Windows 8.1 (such as the camera). However, there are quite a few that your can. We do  this via a powershell script.

$AppList = "Microsoft.BingFinance",

ForEach ($App in $AppList) {
   $AppxPackage = Get-AppxProvisionedPackage -online | Where {$_.DisplayName -eq $App}
   Remove-AppxProvisionedPackage -online -packagename ($AppxPackage.PackageName)
   Remove-AppxPackage ($AppxPackage.PackageName)

 Copy a Start Screen layout to the default user profile

Using a test device, create the Start screen layout that you're looking for (grouping, naming etc). Then grab the %AppData%\Local\Microsoft\Windows\AppsFolderLayout.bin file and drop it in a package for deployment to the default user profile.

xcopy ".\*.*" "C:\Users\Default\AppData\Local\Microsoft\Windows" /D /E /C /I /Q /H /R /Y /S

Create and Import Customised Tiles

We used a Windows 8 app called Obly Tile to create a series of new start screen tiles for our core company applications and intranet sites. Along with the previous two steps, the end results gives us a very streamlined Start Screen with familiar icons for users. Once you have created your titles, create a package out of the Obly Tile application, source folder structure it creates and an use a simple batch script to copy into the default Start Menu

if not exist "C:\Program Files\OblyTile" md "C:\Program Files\OblyTile" 
xcopy "OblyTile\*.*" "C:\Program Files\OblyTile" /s /y
xcopy "Start Menu\*.*" "C:\ProgramData\Microsoft\Windows\Start Menu" /s /y

Application Association

Some file types, such a JPEGs for example, may be associated with apps that you don't want to use. One way to update this is by updating the associations first on a test device, then exporting the AppAssoc.xml file. NB. This only works for new user profiles on that device.

"dism /online /Export-DefaultAppAssociations:C:\temp\AppAssoc.xml"

Add the xml file and batch file to import it into to your package.

"dism /online /Import-DefaultAppAssociations:AppAssoc.xml"

Group Policy

Once the machine has been deployed we implement a number of Group Policy settings to customise our final image. Every company is different, so these are just what works for us.

We typically have three Computer Policies; one for all SOE computers, then one each for the handful of special tweaks relating to either Windows 7 or Windows 8. Make sure you grab the latest Windows 8.1 ADMX files from a test build and import into Active Directory GP.

Separate Windows 7 and Windows 8 Profiles

There can be some potential corruptions between the two profile version, plus we wanted new profiles to ensure we got a consistent Start Screen experience for new users. Most of the users items such as Desktop, Favorites, My Documents etc are taken care of with Folder redirection. So by using the technique below, we were able to create separate profiles for our users, allowing them to switch back and forth between Windows 7 desktops and Windows 8 tablets and laptops.

In both the Windows 7 and Windows 8 Group Policies create a System Environment Variable (Preferences | Windows Settings | Environment Variables) and called in OSVer, with a Value of Win7 or Win8 respectively.

Then in Active Directly, set up their profile path to use the variable.


Computer Policies

Force Internet Explorer to open in Desktop mode
Windows Components/Internet Explorer/Internet Settings
Set how links are opened in Internet Explorer =  Always in Internet Explorer

Disable SkyDrive
Windows Components/SkyDrive
Prevent the usage of SkyDrive for file storage =  Enabled  

Disable Windows Store
Windows Components/Store
Turn off the Store application = Enabled 

Allow local powershell scripts to run (eg logon.ps1 script)
Windows Components/Windows PowerShell
Turn on Script Execution = Enabled  
Execution Policy Allow local scripts and remote signed scripts 

Computer Preferences

Remove First Use Animation
Action Create 
Key path SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System 
Value name EnableFirstLogonAnimation 
Value type REG_DWORD 
Value data 0x0 (0) 

User Policies

Disable Edge Help Tips
Windows Components/Edge UIhide
Disable help tips Enabled

Disable IE SPDY/3 network protocol
Windows Components/Internet Explorer/Internet Control Panel/Advanced Page
Allow Internet Explorer to use the SPDY/3 network protocol Disabled

User Preferences

Boot to Desktop
Action Replace 
Key path Software\Microsoft\Windows\CurrentVersion\Explorer\StartPage\ 
Value name OpenAtLogon 
Value type REG_DWORD 
Value data 0x0 (0) 

Disable DPI Scaling
Action Replace 
Key path Control Panel\Desktop 
Value name Win8DpiScaling 
Value type REG_DWORD 
Value data 0x1 (1)  

Set DPI pixels

Action Replace 
Key path Control Panel\Desktop 
Value name LogPixels 
Value type REG_DWORD 
Value data 0x60 (96)

This is probably going to be an ongoing project and I'm sure others have some great tips, so I'l keep updating as they come in.

         (oo)  ok
   /------\/  /
  / |    ||
 *  /\---/\
    ^^   ^^