Don’t wrap DsRegCmd with PowerShell – Use this to get Azure AD information from the local computer

Posted by

I don’t know how many times I’ve seen scripts that is trying to wrap dsregcmd /status output to get information such as tenant Id, check if the computer is joined to Azure AD and more

But isn’t there a better way? So started looking, for sure, some information are found in registry, but that’s a bit legacy 🙂 I need a better way
Some years ago I was playing around with PowerShell and C# code, basically you run C# code in your PowerShell script on the run, without the need of compiling it before. This gives us tons of options and especially you are able to use the Windows API, a bit tricky but still much better.

So I turned to the Win32 API and found this: https://docs.microsoft.com/en-us/windows/win32/api/lmjoin/nf-lmjoin-netgetaadjoininformation
and that looked promising for sure!

What I want to accomplish is simply a better way of getting the dsregcmd /status information, without wrapping the text and stuff.. so here it is!

The script can be found on my GitHub where you are able to improve it or a more static version below

<#PSScriptInfo
.VERSION 1.0
.GUID 
.AUTHOR Mattias Fors
.COMPANYNAME DeployWindows.com
.COPYRIGHT 
.TAGS Windows AzureAD TenantID AAD AADJ ADJ AD DeviceID
.LICENSEURI 
.PROJECTURI 
.ICONURI 
.EXTERNALMODULEDEPENDENCIES 
.REQUIREDSCRIPTS 
.EXTERNALSCRIPTDEPENDENCIES 
.RELEASENOTES
Version 1.0:  Original
#>

<#
.SYNOPSIS
Get information from the local computer such as Azure AD join status, tenant Id, device id
.DESCRIPTION
Get information from the local computer such as Azure AD join status, tenant Id, device id and such. Similar information as dsregcmd /status
.EXAMPLE
.\Get-AadJoinInformation.ps1
#>

Add-Type -TypeDefinition @'
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

public class NetAPI32{
    public enum DSREG_JOIN_TYPE {
      DSREG_UNKNOWN_JOIN,
      DSREG_DEVICE_JOIN,
      DSREG_WORKPLACE_JOIN
    }

	[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
    public struct DSREG_USER_INFO {
        [MarshalAs(UnmanagedType.LPWStr)] public string UserEmail;
        [MarshalAs(UnmanagedType.LPWStr)] public string UserKeyId;
        [MarshalAs(UnmanagedType.LPWStr)] public string UserKeyName;
    }

    [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
    public struct CERT_CONTEX {
        public uint   dwCertEncodingType;
        public byte   pbCertEncoded;
        public uint   cbCertEncoded;
        public IntPtr pCertInfo;
        public IntPtr hCertStore;
    }

	[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
    public struct DSREG_JOIN_INFO
    {
        public int joinType;
        public IntPtr pJoinCertificate;
        [MarshalAs(UnmanagedType.LPWStr)] public string DeviceId;
        [MarshalAs(UnmanagedType.LPWStr)] public string IdpDomain;
        [MarshalAs(UnmanagedType.LPWStr)] public string TenantId;
        [MarshalAs(UnmanagedType.LPWStr)] public string JoinUserEmail;
        [MarshalAs(UnmanagedType.LPWStr)] public string TenantDisplayName;
        [MarshalAs(UnmanagedType.LPWStr)] public string MdmEnrollmentUrl;
        [MarshalAs(UnmanagedType.LPWStr)] public string MdmTermsOfUseUrl;
        [MarshalAs(UnmanagedType.LPWStr)] public string MdmComplianceUrl;
        [MarshalAs(UnmanagedType.LPWStr)] public string UserSettingSyncUrl;
        public IntPtr pUserInfo;
    }

    [DllImport("netapi32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
    public static extern void NetFreeAadJoinInformation(
            IntPtr pJoinInfo);

    [DllImport("netapi32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
    public static extern int NetGetAadJoinInformation(
            string pcszTenantId,
            out IntPtr ppJoinInfo);
}
'@

$pcszTenantId = $null
$ptrJoinInfo = [IntPtr]::Zero

# https://docs.microsoft.com/en-us/windows/win32/api/lmjoin/nf-lmjoin-netgetaadjoininformation
#[NetAPI32]::NetFreeAadJoinInformation([IntPtr]::Zero);
$retValue = [NetAPI32]::NetGetAadJoinInformation($pcszTenantId, [ref]$ptrJoinInfo);

# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/18d8fbe8-a967-4f1c-ae50-99ca8e491d2d
if ($retValue -eq 0) 
{
    # https://support.microsoft.com/en-us/help/2909958/exceptions-in-windows-powershell-other-dynamic-languages-and-dynamical

    $ptrJoinInfoObject = New-Object NetAPI32+DSREG_JOIN_INFO
    $joinInfo = [System.Runtime.InteropServices.Marshal]::PtrToStructure($ptrJoinInfo, [System.Type] $ptrJoinInfoObject.GetType())
    $joinInfo | fl

    $ptrUserInfo = $joinInfo.pUserInfo
    $ptrUserInfoObject = New-Object NetAPI32+DSREG_USER_INFO
    $userInfo = [System.Runtime.InteropServices.Marshal]::PtrToStructure($ptrUserInfo, [System.Type] $ptrUserInfoObject.GetType())
    $userInfo | fl

    Write-Host "Device is $([NetAPI32+DSREG_JOIN_TYPE]($joinInfo.joinType))"
    switch ($joinInfo.joinType)
    {
        ([NetAPI32+DSREG_JOIN_TYPE]::DSREG_DEVICE_JOIN.value__)    { Write-Host "Device is joined" }
        ([NetAPI32+DSREG_JOIN_TYPE]::DSREG_UNKNOWN_JOIN.value__)   { Write-Host "Device is not joined, or unknown type" }
        ([NetAPI32+DSREG_JOIN_TYPE]::DSREG_WORKPLACE_JOIN.value__) { Write-Host "Device workplace joined" }
    }

    $ptrJoinCertificate = $joinInfo.pJoinCertificate
    $ptrJoinCertificateObject = New-Object NetAPI32+CERT_CONTEX
    $joinCertificate = [System.Runtime.InteropServices.Marshal]::PtrToStructure($ptrJoinCertificate, [System.Type] $ptrJoinCertificateObject.GetType())
    #$JoinCertificate | fl

    #Release pointers
    [System.Runtime.InterOpServices.Marshal]::Release($ptrJoinInfo) | Out-Null
    [System.Runtime.InterOpServices.Marshal]::Release($ptrUserInfo) | Out-Null
    [System.Runtime.InterOpServices.Marshal]::Release($ptrJoinCertificate) | Out-Null
}
else
{
    Write-Host "Not Azure Joined"
}

Leave a comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.