Back
Featured image of post PowerShell: Azure Active Directory (AAD) Connect tooling

PowerShell: Azure Active Directory (AAD) Connect tooling

During a customer workshop, we developed a few neat AAD Connect tools.

Here are some of them, they could be the base for your own tools.

#requires -Version 2.0

function Invoke-ADSyncDeltaCycle
{
    <#
            .SYNOPSIS
          Trigger an AAD Connect Sync

            .DESCRIPTION
          Trigger an AAD Connect Sync on a remote computer

            .PARAMETER AADComputer
          The computer where AAD Connect is installed, needs to be an FQDN

            .EXAMPLE
          PS C:\> Invoke-ADSyncDeltaCycle -AADComputer 'aad.domain.tld'

          Trigger an AAD Connect Sync on aad.domain.tld

            .NOTES
          Showcase: We do NOT use the old school import of "C:\Program Files\Microsoft Azure AD Sync\Bin\ADSync\ADSync.psd1"
  #>

	param
	(
		[Parameter(ValueFromPipeline = $true,
					  Position = 1)]
		[ValidateNotNullOrEmpty()]
		[ValidatePattern('(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)')]
		[string]
		$AADComputer = 'default.domain.tld'
	)

	begin
	{
		try
		{
			# Create a remote powerShell session.
			$session = (New-PSSession -ComputerName $AADComputer)

			Write-Verbose -Message "Session created on $AADComputer"
		}
		catch
		{
			# Doh! Did you enable remote Powerhell by executing the following:
			# Enable-PSRemoting -Force
			#
			# Did you check the Firewall on the remote system?
			# Remember?
			# Enable-NetFirewallRule -DisplayGroup 'Windows Remote Management (Compatibility)' -PolicyStore PersistentStore
			Write-Error -Message ('{0} - Line Number: {1}' -f $_.Exception.Message, $_.InvocationInfo.ScriptLineNumber) -ErrorAction Stop
		}
	}

	process
	{
		try
		{
			# If you want to use this within the remote session, you have to tell the REMOTE computer,
			# that debug/verbose should be displayed. Not only the local one!
			Write-Verbose -Message "Start AD Sync on $AADComputer"

			Invoke-Command -Session $session -ScriptBlock {
				try
				{
					# Import the remote(!) Module, not on the local system...
					$null = (Import-Module -Name 'ADSync' -Force -ErrorAction Stop -WarningAction SilentlyContinue)

					# Execute the command we want on the remote system
					$null = (Start-ADSyncSyncCycle -PolicyType Delta -InteractiveMode $false -ErrorAction Stop -WarningAction SilentlyContinue)
				}
				catch
				{
					# No Error Handling on the remote system
					# Could be a simple throw, as you like
					Write-Error -Message $_.Exception.Message -ErrorAction Stop
				}
			}

			Write-Verbose -Message "Done with the AD Sync on $AADComputer"
		}
		catch
		{
			# Cleanup, if needed
			if ($session)
			{
				$null = (Remove-PSSession -Id $session.Id)
			}

			# Here is the REAL Error handling: On the local system.
			# You might want to retry something or inform someone... Mail? Eventlog?
			Write-Error -Message ('{0} - Line Number: {1}' -f $_.Exception.Message, $_.InvocationInfo.ScriptLineNumber) -ErrorAction Stop
		}
	}

	end
	{
		if ($session)
		{
			Write-Verbose -Message 'Cleanup the session'

			# Something you should do: Do NOT leave the remote session open and stop executing
			# Yep, it will get a timeout, but you shoul cleanup the session as soon as you are sone!
			$null = (Remove-PSSession -Id $session.Id)
		}
	}
}

function Invoke-ADSyncCleanup
{
    <#
            .SYNOPSIS
          Trigger an AAD Connect cleanup

            .DESCRIPTION
          Trigger an AAD Connect cleanup on a remote computer

            .PARAMETER AADComputer
          The computer where AAD Connect is installed, needs to be an FQDN

            .PARAMETER Days
          timespan that we pass to the PurgeRunHistoryInterval parameter. The default is 7

            .EXAMPLE
          PS C:\> Invoke-ADSyncCleanup -AADComputer 'aad.enatec.local' -Days '30'

          Removes the entries of the last 30 days from the logs of the host aad.enatec.local

            .NOTES
          Helper function to keep the system clean ;-)
  #>

	param
	(
		[Parameter(ValueFromPipeline = $true,
					  Position = 1)]
		[ValidateNotNullOrEmpty()]
		[ValidatePattern('(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)')]
		[string]
		$AADComputer = 'default.domain.tld',
		[Parameter(ValueFromPipeline = $true,
					  Position = 2)]
		[ValidateNotNullOrEmpty()]
		$Days = '7'
	)

	begin
	{
		try
		{
			# Open the remote session
			$session = (New-PSSession -ComputerName $AADComputer)

			Write-Verbose -Message "Session created on $AADComputer"
		}
		catch
		{
			# Whoopsie, unable to open the remote session!
			Write-Error -Message ('{0} - Line Number: {1}' -f $_.Exception.Message, $_.InvocationInfo.ScriptLineNumber) -ErrorAction Stop
		}
	}

	process
	{
		try
		{
			Write-Verbose -Message "Cleanup the last $Days of logs on $AADComputer"

            <#
                  The difference here:
                  We use the 'param' and the 'ArgumentList' to handover the value of 'days' to the remote computer.
                  You might want to use a variable that you declaire on your LOCAL system,
                  That variable can conaint any local variable you like and will be executed on the
                  remote system (think: Like a simple string that is already filled)
          #>

			Invoke-Command -Session $session -ScriptBlock {
				param ($Days)
				try
				{
					$null = (Import-Module -Name 'ADSync' -Force -ErrorAction Stop -WarningAction SilentlyContinue)

					# Execute the command we want (remote)
					Start-ADSyncPurgeRunHistory -PurgeRunHistoryInterval $Days
				}
				catch
				{
					# No Error Handling on the remote system
					# Could be a simple throw, as you like
					Write-Error -Message $_.Exception.Message -ErrorAction Stop
				}
			} -ArgumentList $Days
			# The ArgumentList the what the remote site get in the param.
			# We use this to handover the Variable from local to the remote system.
			# See the comment above the ScriptBlock
		}
		catch
		{
			# Cleanup, if needed
			if ($session)
			{
				$null = (Remove-PSSession -Id $session.Id)
			}

			# Whoopsie!
			Write-Error -Message ('{0} - Line Number: {1}' -f $_.Exception.Message, $_.InvocationInfo.ScriptLineNumber) -ErrorAction Stop
		}
	}

	end
	{
		if ($session)
		{
			Write-Verbose -Message 'Cleanup the session'
			$null = (Remove-PSSession -Id $session.Id)
		}
	}
}

<#
      Azure Active Directory (AAD) Connect tooling.

      With Azure Active Directory (AAD) connect you can syncronize an On-Premises Active Directory with the Microsoft Cloud.
      You might want to do that if you use Office 365 or any other Azure based Microsoft Cloud Service.

      Requirements to use the functions:
      - Domain joined PC (if not, you need to tweak a bit. But it IS possible!)
      - Remote PowerShell is enabled on the remote system (Execute "Enable-PSRemoting -Force")
      - The Firewalls allow the communication.

      The functions in this script where created during a PowerShell workshop with the admin of a customer.
      The admins asked for a solution and we created this approach from scratch together.
      We developed a lot of functions around the AAD Connect and ADFS Tools from Microsoft.

      They searched and found sample code and snippets where the "C:\Program Files\Microsoft Azure AD Sync\Bin\ADSync\ADSync.psd1" is parsed.
      That still works, but newer ADSync versions do NOT need that anymore and by using a native "Import-Module -Name 'ADSync'" you might be
      more flexible in the future and you do not need to know where the PSD1 file is on the remote computer.

      You might ask yourself: Why is the code not optimized and have so many comments?
      The answer: This is code to teach others how to build simple tools ;-) Teaching without so much comments makes no sense, correct?

      And trust me: It was way more complex during the workshop!!! and I mean a lot more...

      Nothing fancy, but it should to the job!

      TODO: We need to find a better REGEX for the FQDN check! The one we use is not perfect and might need some tweaks!
#>

<#
      Copyright (c) 2017, Joerg Hochwald
      All rights reserved.

      Redistribution and use in source and binary forms, with or without
      modification, are permitted provided that the following conditions are met:

      1. Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.

      2. Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.

      3. Neither the name of the copyright holder nor the names of its contributors
      may be used to endorse or promote products derived from this software without
      specific prior written permission.

      THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
      AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
      IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
      ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
      LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
      DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
      OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
      CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
      OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
      OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

      By using the Software, you agree to the License, Terms and Conditions above!
#>

<#
      DISCALIMER:
      This is a third party Software!

      The developer of this Software is NOT sponsored by or affiliated with
      Microsoft Corp (MSFT) or any of it's subsidiaries in any way

      The Software is not supported by Microsoft Corp (MSFT)!
#>

You might then want to take a look here (the real source).

With Azure Active Directory (AAD) connect you can synchronise an On-Premises Active Directory with the Microsoft Cloud.

And like most modern Microsoft Tool itĀ haveĀ great PowerShell support. Something Microsoft started a while ago.