WaaSAssessment.psm1

<#
 
PSWaaS
 
Copyright (C) 2025 Vincent Anso
 
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
 
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 
 
For more information :
 
WaaS Assessment Platform
 
https://learn.microsoft.com/en-us/windows/win32/sysinfo/update-assessor-service
 
#>


#Requires -Version 5.1
#Requires -RunAsAdministrator

using namespace System.Text
using namespace System.ComponentModel
using namespace System.Runtime.InteropServices
using namespace WaaSAssessment

# When $IsWindows doesn't exist on PowerShell 5.x
if ($null -eq $PSVersionTable.Platform) 
{
    $IsWindows = $true
}

if ( -Not $IsWindows )
{
    Write-Warning "This module only runs on Windows."
    exit 0
}

$sourceCode = @"
using System;
using System.Text;
using System.Runtime.InteropServices;
using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
 
namespace WaaSAssessment
{
    public static class Win32
    {
        public const uint LOCALE_USER_DEFAULT = 0x0400;
 
        [StructLayout(LayoutKind.Sequential)]
        public struct SYSTEMTIME
        {
            public ushort wYear;
            public ushort wMonth;
            public ushort wDayOfWeek;
            public ushort wDay;
            public ushort wHour;
            public ushort wMinute;
            public ushort wSecond;
            public ushort wMilliseconds;
        }
 
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool FileTimeToSystemTime(ref FILETIME fileTime, out SYSTEMTIME systemTime);
 
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern int GetDateFormat(uint locale, uint flags, ref SYSTEMTIME systemTime, string format, StringBuilder dateStr, int dateSize);
 
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern int GetTimeFormat(uint locale, uint flags, ref SYSTEMTIME systemTime, string format, StringBuilder timeStr, int timeSize);
    }
 
    // Define the COM interface IWaaSAssessor
    [ComImport]
    [Guid("2347bbef-1a3b-45a4-902d-3e09c269b45e")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IWaaSAssessor
    {
        int GetOSUpdateAssessment(ref OSUpdateAssessment result);
    }
 
    // Define the COM class WaaSAssessor
    [ComImport]
    [Guid("098ef871-fa9f-46af-8958-c083515d7c9c")]
    [ClassInterface(ClassInterfaceType.None)]
    public class WaaSAssessor
    {
    }
 
    // Enum representation of UpdateImpactLevel
    public enum UpdateImpactLevel : uint
    {
        None = 0,
        Low = None + 1,
        Medium = Low + 1,
        High = Medium + 1
    }
 
    // Enum representation of UpdateAssessmentStatus
    public enum UpdateAssessmentStatus : uint
    {
        Latest = 0,
        NotLatestSoftRestriction = Latest + 1,
        NotLatestHardRestriction = NotLatestSoftRestriction + 1,
        NotLatestEndOfSupport = NotLatestHardRestriction + 1,
        NotLatestServicingTrain = NotLatestEndOfSupport + 1,
        NotLatestDeferredFeature = NotLatestServicingTrain + 1,
        NotLatestDeferredQuality = NotLatestDeferredFeature + 1,
        NotLatestPausedFeature = NotLatestDeferredQuality + 1,
        NotLatestPausedQuality = NotLatestPausedFeature + 1,
        NotLatestManaged = NotLatestPausedQuality + 1,
        NotLatestUnknown = NotLatestManaged + 1,
        NotLatestTargetedVersion = NotLatestUnknown + 1
    }
 
    // Struct representation of UpdateAssessment
    [StructLayout(LayoutKind.Sequential)]
    public struct UpdateAssessment
    {
        public UpdateAssessmentStatus Status;
        public UpdateImpactLevel Impact;
        public uint DaysOutOfDate;
    }
 
    // Struct representation of OSUpdateAssessment
    [StructLayout(LayoutKind.Sequential)]
    public struct OSUpdateAssessment
    {
        [MarshalAs(UnmanagedType.Bool)]
        public bool IsEndOfSupport;
        public UpdateAssessment AssessmentForCurrent;
        public UpdateAssessment AssessmentForUpToDate;
        public UpdateAssessmentStatus SecurityStatus;
        public FILETIME AssessmentTime;
        public FILETIME ReleaseInfoTime;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string CurrentOSBuild;
        public FILETIME CurrentOSReleaseTime;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string UpToDateOSBuild;
        public FILETIME UpToDateOSReleaseTime;
    }
 
    public static class WaaSAssessorHelper
    {
        public static int GetOSUpdateAssessment(ref OSUpdateAssessment result)
        {
            IWaaSAssessor assessor = null;
 
            try
            {
                assessor = (IWaaSAssessor)new WaaSAssessor();
                int hr = assessor.GetOSUpdateAssessment(ref result);
                return hr;
            }
            catch (Exception ex)
            {
                return ex.HResult;
            }
        }
    }
}
"@


Add-Type -TypeDefinition $sourceCode

function ConvertFrom-FILETIME
{
      param (
        [Parameter(Mandatory = $true)]
        [ComTypes.FILETIME]$FileTime
    )

    $invalidDate = [DateTime]::new(1601, 1, 1)

    $systemTime = New-Object Win32+SYSTEMTIME
        
    if ( [Win32]::FileTimeToSystemTime([ref]$FileTime, [ref]$systemTime) )
    {
        $currentCulture = Get-Culture
        
        $shortDatePattern = $currentCulture.DateTimeFormat.ShortDatePattern
        $longTimePattern = $currentCulture.DateTimeFormat.LongTimePattern

        $dateBuffer = [StringBuilder]::new()
        $timeBuffer = [StringBuilder]::new()

        $dateSize = [Win32]::GetDateFormat([Win32]::LOCALE_USER_DEFAULT, 0, [ref]$systemTime, $shortDatePattern, $dateBuffer, $dateBuffer.Capacity)
        $timeSize = [Win32]::GetTimeFormat([Win32]::LOCALE_USER_DEFAULT, 0, [ref]$systemTime, $longTimePattern , $timeBuffer, $timeBuffer.Capacity)

        $dateTime = [DateTime]::new(0)

        if ( [DateTime]::TryParse("$dateBuffer $timeBuffer", [ref]$dateTime) )
        {
            if ( $dateTime -gt $( [DateTime]::new(2006, 11, 14) ) )
            {
                return $dateTime
            }
        }
    }
    return $invalidDate
}

function Get-OSUpdateAssessment
{
    <#
     
    .SYNOPSIS
    Gets WaaS Assessment Platform information.
 
    #>

    
    param(
        [switch]$Raw
    )
    
    $result = [OSUpdateAssessment]::new()

    $hr = [WaaSAssessorHelper]::GetOSUpdateAssessment([ref]$result)

    if ($hr -ne 0x0)
    {
        [Win32Exception]::new($hr)

        exit $hr
    }

    if ( $PSBoundParameters.ContainsKey("Raw") )
    {
        $result
    }
    else 
    {
        [PSCustomObject]@{
        IsEndOfSupport        = $result.IsEndOfSupport
        AssessmentForCurrent  = @{ Status     = $result.AssessmentForCurrent.Status
                                Impact        = $result.AssessmentForCurrent.Impact
                                DaysOutOfDate = $result.AssessmentForCurrent.DaysOutOfDate
                                }    
        AssessmentForUpToDate = @{ Status     = $result.AssessmentForUpToDate.Status
                                Impact        = $result.AssessmentForUpToDate.Impact
                                DaysOutOfDate = $result.AssessmentForUpToDate.DaysOutOfDate
                                }  
        SecurityStatus        = $result.SecurityStatus
        AssessmentTime        = ConvertFrom-FILETIME $result.AssessmentTime
        ReleaseInfoTime       = ConvertFrom-FILETIME $result.ReleaseInfoTime
        CurrentOSBuild        = [Version]$result.CurrentOSBuild
        CurrentOSReleaseTime  = ConvertFrom-FILETIME $result.CurrentOSReleaseTime
        UpToDateOSBuild       = [Version]$result.UpToDateOSBuild
        UpToDateOSReleaseTime = ConvertFrom-FILETIME $result.UpToDateOSReleaseTime
        }
    }
}

Export-ModuleMember -Function @('Get-OSUpdateAssessment')