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.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
#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)!
#>

If you see nothing above, your computer might blocked the Gist content. 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.