Retrieving Local Logon Events from the SCCM Client WMI

Usually when querying the logon history of a Windows system you might query the Security event log or a domain controller. But if you’re using SCCM, the SCCM client also logs user logon events and stores them in WMI. Here’s a quick PowerShell script to retrieve those events and translate them into meaningful values.

You can run it against the local or a remote computer and optionally specify the maximum number of events to retrieve.

Note that for remote computers the date/time values will be displayed in your local time zone, not necessarily the timezone of the remote system.

Get-CMUserLogonEvents | Sort LogonTime -Descending | Out-GridView

Just for Fun – Send a Remote Toast Notification

Did you know you can send a custom toast notification to a remote computer? Call it poor man’s IM, but if you’re using Windows 10 with PowerShell remoting enabled it might be a good way to annoy your colleagues if you can’t find a more constructive use!

Try the following code, which creates a notification like this on your mate’s computer:

Create Collections for SCCM Software Update Installation Failures by Error Code

Recently I published a blog about creating collections for SCCM client installation failures by error code. In this post, I will do the same for Software Update installation failures.

If you’re lucky enough not to have any errors installing software updates with SCCM, then this post isn’t for you, but if you do experience installation failures it can be helpful to collate machines with the same error into collections so you can easily target them for remediation using the SCCM Scripts feature for example, or just for visibility and reporting.

To find which software update installation errors you are experiencing in your environment, you can run the following SQL query against the SCCM database. This will find systems in the “Error” or “Unknown” enforcement states for software update deployments and group them by the enforcement error code.

Select Count(ResourceID),LastEnforcementErrorCode
from vSMS_SUMDeploymentStatusPerAsset 
where StatusType in (4,5)
and LastEnforcementErrorCode is not null
Group by LastEnforcementErrorCode

Next is a PowerShell script that will create collections for each error code. You need to specify the error codes in the Error Code translation table in the script. I’ve included some common error codes for software updates and their friendly descriptions – add or remove error codes according to your own environment. To translate error codes to friendly descriptions, see here. Run the script on a site server or anywhere with the SCCM console installed.

I’ve split the collections between those with an “error” enforcement state and those with “unknown” as you may wish to handle them separately, and placed the collections for each state in different sub-folders.

You may wish to be more targeted in the WQL query for the collection rule, targeting only certain collections or deployments etc. For example, you can add a ‘where’ clause for SUM.CollectionName to target particular collections, or SUM.AssignmentName to target specific SUG deployments.

Here’s what the end result will look like. The error description is added to the Comment field, so just add that in the console view.

Find the Full Windows Build Number with PowerShell

Much to my surprise I discovered that the full build number for a Windows OS is not stored in WMI in the usual Win32_OperatingSystem class.

In Windows 10 at least, the full build number containing the “UBR”, or essentially the CU patch level of the build, is a useful piece of information.

Open Settings > System > About on a Windows 10 box, and you’ll find the OS Build value, in my case 15063.183

W10

If I run the usual WMI query to get the build number I just get 15063:

WMI

Same if I query the environment:

DotNet

To find the full number I have to query the registry in the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion branch.

So I put together a PowerShell script that can be used to get the Windows version for a local or remote computer (or group of computers) which includes the Edition, Version and full OS Build values.

Query the local system like this:


Get-WindowsVersion

Or query remote computers:


Get-WindowsVersion -ComputerName PC001


Get-WindowsVersion -ComputerName @("PC001","PC002","SRV001","SRV002")

Result:

result

The script

Creating Simple Charts in WPF with PowerShell

Windows Presentation Foundation (WPF) is great for creating GUI applications, but it does not natively contain any charting controls.  There are a number of products that can be used to create charts in WPF, including the WPF toolkit and the Microsoft Chart Controls for .Net, but good-old Windows Forms does this natively.

WPF has does have a WindowsFormsHost control, but there are a number of potential issues with hosting Windows Forms in a WPF application, and it not recommended practice.

After some playing around however, I found it is possible to add a Windows Forms chart simply by displaying it as an image.  Furthermore, it is also possible to save the image to a memory stream in binary format, which means it does not need to be saved to disk as a file, but can simply be stored and read from memory in binary form.

Below is a simple example of a Windows Forms pie chart added as an image to a WPF window using PowerShell.  It calculates the used and available RAM in the local system, creates a pie chart from the data, saves it as in image in binary form, then adds it as the source to an image control in the WPF window.  Pretty cool 🙂

chart


# Add required assemblies
Add-Type -AssemblyName PresentationFramework,System.Windows.Forms,System.Windows.Forms.DataVisualization

# Create WPF window
[xml]$xaml = @"
<Window          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"         Title="Chart Example" Height="350" Width="420">
    <Grid>
        <Image x:Name="image" HorizontalAlignment="Left" Height="auto" VerticalAlignment="Top" Width="auto"/>
    </Grid>
</Window>

"@

# Add window and it's named elements to a hash table
$script:hash = @{}
$hash.Window = [Windows.Markup.XamlReader]::Load((New-Object -TypeName System.Xml.XmlNodeReader -ArgumentList $xaml))
$xaml.SelectNodes("//*[@*[contains(translate(name(.),'n','N'),'Name')]]") | ForEach-Object -Process {
    $hash.$($_.Name) = $hash.Window.FindName($_.Name)
}

# Function to create a Windows Forms pie chart
# Modified from https://www.simple-talk.com/sysadmin/powershell/building-a-daily-systems-report-email-with-powershell/
Function Create-PieChart() {
    param([hashtable]$Params)

    #Create our chart object
    $Chart = New-object System.Windows.Forms.DataVisualization.Charting.Chart
    $Chart.Width = 430
    $Chart.Height = 330
    $Chart.Left = 10
    $Chart.Top = 10

    #Create a chartarea to draw on and add this to the chart
    $ChartArea = New-Object System.Windows.Forms.DataVisualization.Charting.ChartArea
    $Chart.ChartAreas.Add($ChartArea)
    [void]$Chart.Series.Add("Data") 

    #Add a datapoint for each value specified in the parameter hash table
    $Params.GetEnumerator() | foreach {
        $datapoint = new-object System.Windows.Forms.DataVisualization.Charting.DataPoint(0, $_.Value.Value)
        $datapoint.AxisLabel = "$($_.Value.Header)" + "(" + $($_.Value.Value) + " GB)"
        $Chart.Series["Data"].Points.Add($datapoint)
    }

    $Chart.Series["Data"].ChartType = [System.Windows.Forms.DataVisualization.Charting.SeriesChartType]::Pie
    $Chart.Series["Data"]["PieLabelStyle"] = "Outside"
    $Chart.Series["Data"]["PieLineColor"] = "Black"
    $Chart.Series["Data"]["PieDrawingStyle"] = "Concave"
    ($Chart.Series["Data"].Points.FindMaxByValue())["Exploded"] = $true

    #Set the title of the Chart
    $Title = new-object System.Windows.Forms.DataVisualization.Charting.Title
    $Chart.Titles.Add($Title)
    $Chart.Titles[0].Text = "RAM Usage Chart ($($env:COMPUTERNAME))"

    #Save the chart to a memory stream, then to the hash table as a byte array
    $Stream = New-Object System.IO.MemoryStream
    $Chart.SaveImage($Stream,"png")
    $Hash.Stream = $Stream.GetBuffer()
    $Stream.Dispose()
}

# Add an event to display the chart when the window is opened
$hash.Window.Add_ContentRendered({
    # Create a hash table to store values
    $Params = @{}
    # Get local RAM usage from WMI
    $RAM = (Get-CimInstance -ClassName Win32_OperatingSystem -Property TotalVisibleMemorySize,FreePhysicalMemory)
    # Add Free RAM to a hash table
    $Params.FreeRam = @{}
    $Params.FreeRam.Header = "Free RAM"
    $Params.FreeRam.Value = [math]::Round(($RAM.FreePhysicalMemory / 1MB),2)
    # Add used RAM to a hash table
    $Params.UsedRam = @{}
    $Params.UsedRam.Header = "Used RAM"
    $Params.UsedRam.Value = [math]::Round((($RAM.TotalVisibleMemorySize / 1MB) - ($RAM.FreePhysicalMemory / 1MB)),2)
    # Create the Chart
    Create-PieChart $Params
    # Set the image source
    $Hash.image.Source = $hash.Stream
})

# Display window
$null = $hash.Window.ShowDialog()

Creating WPF GUI Applications with Pure PowerShell

When creating a GUI application in PowerShell, I usually use Visual Studio, or Blend for Visual Studio, to design a WPF application, then copy and run the XAML code in PowerShell.  Designing in VS is generally easier and quicker and creates less code, but it is also perfectly possible to create a WPF GUI using pure PowerShell, which is more akin to the Windows Forms method of GUI creation.  For more complex applications that’s not the ideal way because it takes longer and creates a lot more code, but for simple applications, or if you are used to designing something in Windows Forms, why not give it a go?  You simply need to create a window from the [System.Windows] .Net namespace, then add some controls from the [System.Windows.Controls] namespace, and you’re away!

Here’s a simple example application that displays the running processes on your machine, the list of services, and a little game “Click the Fruit” as a bonus 😉

capture


# Add required assembly
Add-Type -AssemblyName PresentationFramework

# Create a Window
$Window = New-Object Windows.Window
$Window.Height = "670"
$Window.Width = "700"
$Window.Title = "PowerShell WPF Window"
$window.WindowStartupLocation="CenterScreen"

# Create a grid container with 2 rows, one for the buttons, one for the datagrid
$Grid =  New-Object Windows.Controls.Grid
$Row1 = New-Object Windows.Controls.RowDefinition
$Row2 = New-Object Windows.Controls.RowDefinition
$Row1.Height = "70"
$Row2.Height = "100*"
$grid.RowDefinitions.Add($Row1)
$grid.RowDefinitions.Add($Row2)

# Create a button to get running Processes
$Button_Processes = New-Object Windows.Controls.Button
$Button_Processes.SetValue([Windows.Controls.Grid]::RowProperty,0)
$Button_Processes.Height = "50"
$Button_Processes.Width = "150"
$Button_Processes.Margin = "10,10,10,10"
$Button_Processes.HorizontalAlignment = "Left"
$Button_Processes.VerticalAlignment = "Top"
$Button_Processes.Content = "Get Processes"
$Button_Processes.Background = "Aquamarine"

# Create a button to get services
$Button_Services = New-Object Windows.Controls.Button
$Button_Services.SetValue([Windows.Controls.Grid]::RowProperty,0)
$Button_Services.Height = "50"
$Button_Services.Width = "150"
$Button_Services.Margin = "180,10,10,10"
$Button_Services.HorizontalAlignment = "Left"
$Button_Services.VerticalAlignment = "Top"
$Button_Services.Content = "Get Services"
$Button_Services.Background = "Aquamarine"

# Create a button to play Click the fruit
$Button_Cool = New-Object Windows.Controls.Button
$Button_Cool.SetValue([Windows.Controls.Grid]::RowProperty,0)
$Button_Cool.Height = "50"
$Button_Cool.Width = "150"
$Button_Cool.Margin = "350,10,10,10"
$Button_Cool.HorizontalAlignment = "Left"
$Button_Cool.VerticalAlignment = "Top"
$Button_Cool.Content = "Play 'Click the Fruit'"
$Button_Cool.Background = "Aquamarine"

# Create a datagrid
$DataGrid = New-Object Windows.Controls.DataGrid
$DataGrid.SetValue([Windows.Controls.Grid]::RowProperty,1)
$DataGrid.MinHeight = "100"
$DataGrid.MinWidth = "100"
$DataGrid.Margin = "10,0,10,10"
$DataGrid.HorizontalAlignment = "Stretch"
$DataGrid.VerticalAlignment = "Stretch"
$DataGrid.VerticalScrollBarVisibility = "Auto"
$DataGrid.GridLinesVisibility = "none"
$DataGrid.IsReadOnly = $true

# Add the elements to the relevant parent control
$Grid.AddChild($DataGrid)
$grid.AddChild($Button_Processes)
$grid.AddChild($Button_Services)
$grid.AddChild($Button_Cool)
$window.Content = $Grid

# Add an event on the Get Processes button
$Button_Processes.Add_Click({
    $Processes = Get-Process | Select ProcessName,HandleCount,NonpagedSystemMemorySize,PrivateMemorySize,WorkingSet,UserProcessorTime,Id
    $DataGrid.ItemsSource = $Processes
    })

# Add an event on the Get Services button
$Button_Services.Add_Click({
    $Services = Get-Service | Select Name,ServiceName,Status,StartType
    $DataGrid.ItemsSource = $Services
    })

# Add an event to play Click the fruit
$Button_Cool.Add_Click({

    $Fruit = @{
        Apples = "Green"
        Bananas = "Yellow"
        Oranges = "Orange"
        Plums = "Maroon"
    }

    $Fruit.GetEnumerator() | Foreach {
        # Create a transparent window at a random location on the screen
        $NewWindow = New-Object Windows.Window
        $NewWindow.SizeToContent = "WidthAndHeight"
        $NewWindow.AllowsTransparency = $true
        $NewWindow.WindowStyle = "none"
        $NewWindow.Background = "Transparent"
        $NewWindow.WindowStartupLocation = "Manual"
        $Height = Get-Random -Maximum (([System.Windows.SystemParameters]::PrimaryScreenHeight) - 100)
        $Width = Get-Random -Maximum (([System.Windows.SystemParameters]::PrimaryScreenWidth) - 100)
        $NewWindow.Left = $Width
        $NewWindow.Top = $Height

        # Add a label control for the fruit
        $NewLabel = New-Object Windows.Controls.Label
        $NewLabel.Height = "150"
        $NewLabel.Width = "400"
        $NewLabel.Content = $_.Name
        $NewLabel.FontSize = "100"
        $NewLabel.FontWeight = "Bold"
        $NewLabel.Foreground = $_.Value
        $NewLabel.Background = "Transparent"
        $NewLabel.Opacity = "100"

        # Add an event to close the window when clicked
        $NewWindow.Add_MouseDown({
            $This.Close()
        })

        # Add an event to change the cursor to a hand when the mouse goes over the window
        $NewWindow.Add_MouseEnter({
        $this.Cursor = "Hand"
        })

        # Display the window
        $NewWindow.Content = $NewLabel
        $NewWindow.ShowDialog()
    }
})

# Show the window
if (!$psISE)
{
    # Hide PS console window
    $windowcode = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
    $asyncwindow = Add-Type -MemberDefinition $windowcode -name Win32ShowWindowAsync -namespace Win32Functions -PassThru
    $null = $asyncwindow::ShowWindowAsync((Get-Process -PID $pid).MainWindowHandle, 0) 

    # Run as an application in it's own context
    $app = [Windows.Application]::new()
    $app.Run($Window)
}
Else
{
    [void]$window.ShowDialog()
}

Fixed: ConfigMgr Task Sequence Monitor :)

Good news 🙂  After several people reported that the ConfigMgr Task Sequence Monitor app would close immediately after opening, I investigated and found a small bug in the code that seems to affect only certain configurations.  Today I released version 1.2.1 which addresses this issue, so please download the app again from the following link:

http://gallery.technet.microsoft.com/ConfigMgr-Task-Sequence-fefdc532

Thanks to everyone who reported the bug!

capture