Public/Sync-ADtoO365.ps1
<#
.NOTES Company: BitTitan, Inc. Title: Sync-ADtoO365.ps1 Author: SUPPORT@BITTITAN.COM Requirements: Version: 1.3 Date: DECEMBER 22, 2016 Disclaimer: This script is provided ‘AS IS’. No warrantee is provided either expresses or implied. Copyright: Copyright© 2016 BitTitan. All rights reserved. .SYNOPSIS Synchronizing Active Directory Users, Groups and/or Contacts with Office 365. .DESCRIPTION The script can either run interactively, requiring user input directly into the console, or purely in silent mode by setting the -Silent switch. The script will take the synchronization option (sync and/or delete objects), as well as the object action (which objects to sync [users, group and/or contacts]), and pass them to their corresponding BitTitan Powershell SDK cmdlets to perform the desired synchronization type against the desired object types in Active Directory. Synchronization Actions (-syncAction) options: 0 = 'Simulate without delete' 1 = 'Simulate delete' 2 = 'Sync without delete' 3 = 'Sync with delete' Object Actions (-objectAction) options: 0 = 'Users, Contacts and Groups' 1 = 'Users only' 2 = 'Contacts only' 3 = 'Groups only' .INPUTS System.String System.Int32 System.Management.Automation.Credential System.Diagnostics.Switch .OUTPUTS By default, both log and session transcription files will be written to $env:userprofile\appdata\BitTitanLogging\Sync-ADtoO365* No object model is returned by this cmdlet, as the underlying cmdlets merely write to the console -- though all output information can be found in the transcription file. .EXAMPLE Run purely in SILENT mode, passing credential objects to the -MigrationWizCredentials and -Office365Credentials parameters ($mw = get-credential; $o365 = get-credential). Sync-ADtoO365 -mwCreds $mw -o365Creds $o365 -objectAction 0 -syncAction 0 -Silent Run in INTERACTIVE mode, supply all required values as input, and receive verbose output. Sync-ADtoO365 -Verbose For a table of of the values that correspond with object and/or synchronization options, see DESCRIPTION. #> function Sync-ADtoO365 { [CmdletBinding()] Param ( [Parameter( Mandatory = $true, ParameterSetName = 'Silent' )] [ValidateSet(0,1,2,3)] [int]$syncAction, [Parameter( Mandatory = $true, ParameterSetName = 'Silent' )] [ValidateSet(0,1,2,3)] [int]$objectAction, [Parameter()] [Alias('logDest')] [string]$logDestination = "$env:USERPROFILE\AppData\", [Parameter( Mandatory = $false, ValueFromPipeline = $true, ParameterSetName = 'Interactive' )] [Parameter( Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Silent' )] [ValidateNotNullOrEmpty()] [Alias('mwCreds')] [System.Management.Automation.Credential()]$MigrationWizCredentials, [Parameter( Mandatory = $false, ParameterSetName = 'Interactive' )] [Parameter( Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Silent' )] [ValidateNotNullOrEmpty()] [Alias('O365Creds')] [System.Management.Automation.Credential()]$O365Credentials, [Parameter( Mandatory = $false )] [ValidateSet('BT','China','Beta','Test')] [string]$environment = 'BT', [Parameter( Mandatory = $false, ParameterSetName = 'Silent' )] [switch]$Silent ) # end params Begin {} Process { # get module version for logging configuration $moduleVersion = (Get-Module ADtoO365).version.ToString() # if -Debug switch is specified, just log to console if (($PSBoundParameters.ContainsKey('Debug'))) { Try { Set-loggingConfiguration -NoTextLogging -LogToConsole -RunType 'Debug' -ScriptName ($MyInvocation.MyCommand.Name) -ScriptVersion $moduleVersion -ErrorAction 'Stop' } Catch { throw "Unable to set global logging configuration...aborting." exit } } # elseif -Verbose switch is specified or in Interactive mode, set global logging vars for verbose mode (logs to console and file) elseif (($PSBoundParameters.ContainsKey('Verbose')) -or ($PSCmdlet.ParameterSetName -eq 'Interactive')) { Try { Set-LoggingConfiguration -logFolder $logDestination -logToConsole -RunType 'Customer' -ScriptName ($MyInvocation.MyCommand.Name) -ScriptVersion $moduleVersion -ErrorAction 'Stop' Add-LogEntry -message "Current log directory is: $logDestination" } Catch { throw "Unable to set global logging configuration...aborting" exit } } # else, ONLY log to disk else { Try { Set-LoggingConfiguration -logFolder $logDestination -RunType 'Customer' -ScriptName ($MyInvocation.MyCommand.Name) -ScriptVersion $moduleVersion } Catch { throw "Unable to set global logging configuration...aborting" exit } } Write-Debug 'All parameter validation conditions were met...in Process block.' Disable-SelectMode | Out-Null Try { Import-Module "C:\Program Files (x86)\BitTitan\BitTitan PowerShell\BitTitanPowerShell.dll" -ErrorAction Stop } Catch { Add-logEntry -message "Unable to import BitTitanPowerShell modules from: 'C:\Program Files (x86)\BitTitan\BitTitan PowerShell\BitTitanPowerShell.dll'" -logLevel 'Error' Add-logEntry -message "Error was: $($error[0].exception.message)" -logLevel 'Error' exit } Add-logEntry -message "Current script root path is: $PSScriptRoot" Add-logEntry -message "Looking for file matching '*.config.xml' in $PSScriptRoot" Add-logEntry -message "Validating that there's just one config file in the module root directory" Try { # Failure to import config file shouldn't terminate operation, but should definitely write to the error stream $configFile = Get-ChildItem "$PSScriptRoot\" -Filter "*.config.xml" -Recurse -Verbose -Force -ErrorAction Stop } Catch { Add-logEntry -message "Unable to import config file: $configFile from path: $PSScriptRoot" -logLevel 'Error' Add-logEntry -message "Error was: $($error[0].exception.message)" -logLevel 'Error' } # Validate number of config files in path: $configfile, and import it. $i = 0 foreach ($c in $configFile) { Add-logEntry -message "Found configFile: $c" $i++ } if ( ($i -eq 0) -or ($i -gt 1) ) { Add-logEntry -message "Invalid number of configuration files found in path: $PSScriptRoot. No more than one configuration file can be used." -logLevel 'Error' } else { # Set search filter to empty strings first, as we don't want to terminate when failing to import the config. $global:adServerName = '' $global:userRootSearchContainer = '' $global:userDefaultPassword = '' $global:contactRootSearchContainer = '' $global:groupRootSearchContainer = '' $global:userSearchFilter = '' $global:contactSearchFilter = '' $global:groupSearchFilter = '' $global:userExclusionFilter = '' $global:contactExclusionFilter = '' $global:groupExclusionFilter = '' Try { # Using force parameter in case the user has the file open [xml]$global:config = Get-Content "$PSScriptRoot\$configFile" -ErrorAction Stop -Force -Verbose # declare global config vars here, to pass to SyncFunctions $global:adServerName = $global:config.Settings.adServerName $global:userRootSearchContainer = $global:config.Settings.userRootSearchContainer $global:userDefaultPassword = $global:config.Settings.userDefaultPassword $global:contactRootSearchContainer = $global:config.Settings.contactRootSearchContainer $global:groupRootSearchContainer = $global:config.Settings.groupRootSearchContainer $global:userSearchFilter = $global:config.Settings.userSearchFilter $global:contactSearchFilter = $global:config.Settings.contactSearchFilter $global:groupSearchFilter = $global:config.Settings.groupSearchFilter $global:userExclusionFilter = $global:config.Settings.userExclusionFilter $global:contactExclusionFilter = $global:config.Settings.contactExclusionFilter $global:groupExclusionFilter = $global:config.Settings.groupExclusionFilter # Verbose output for any config parameters that HAVE VALUES (length of element's value is greater than 0) $global:config.Settings.ChildNodes | ForEach-Object { if ($_.'#text'.length -gt 0) { Add-logEntry -message "Using $($_.name) from file $configFile with value: $($_.'#text') " } } # end ForEach-Object Add-logEntry -message "Was able to validate configuration file, using $configFile" Add-logEntry -message "Importing config parameters from $configFile" } Catch { Add-logEntry -message "Unable to import the xml config file from $configFile" -logLevel 'Error' Add-logEntry -message "Error was: $($error[0].exception.message)" -logLevel 'Error' } } # if INTERACTIVE mode, prompt for credentials if ( (-not ($Silent) ) -and ($PSBoundParameters.Keys -notcontains 'MigrationWizCredentials' -and 'O365Credentials') ) { Add-logEntry -message 'First condition met: running in INTERACTIVE mode and prompting user for their O365 and MW credentials' Add-logEntry -message "Running in Interactive Mode...If you wish to run this silently, please see the help file <Get-Help Sync-ADtoO365> for the required parameter values" -logLevel 'Warning' while (($null -eq $MigrationWizCredentials)) { Try { $MigrationWizCredentials = Get-Credential -Message 'Specify your MigrationWiz Credentials' -ErrorAction Stop -Verbose } Catch { Write-Warning "MigrationWiz credentials need to be specified" } } # end while loop for null migrationwiz creds while (($null -eq $O365Credentials)) { Try { $O365Credentials = Get-Credential -Message 'Specify your Office 365 Credentials' -ErrorAction Stop -Verbose } Catch { Write-Warning "O365 Credentials need to be specified" } } # end while loop for null o365 creds } # Running in Interactive mode with credentials already having been specified elseif ( (-not ($Silent) ) -and ($PSBoundParameters.Keys -contains 'MigrationWizCredentials' -and 'O365Credentials') ) { Add-logEntry -message 'Second condition met: running in INTERACTIVE mode with credentials having already been specified...' Add-logEntry -message "Running in INTERACTIVE Mode...If you wish to run this silently, set the -silent option" } else { Add-logEntry -message 'Running in SILENT mode: Neither interactive run-mode conditions were met' } # set environment to pass to Get-BT_Ticket $script:environment = $environment Add-logEntry -message "Current environment is: $script:environment" # set local cred vars $script:migrationwizCredentials = $MigrationWizCredentials $script:O365Credentials = $O365Credentials # SILENT mode: set vars and and perofrm sync. if ($Silent) { Add-logEntry -message 'SILENT mode: Passing SyncAction and ObjectAction integer values to Get-SyncAction and Get-ObjectAction functions' $script:ObjectAction = $objectAction $script:SyncAction = $syncAction Add-logEntry -message "ObjectAction value is: $script:objectAction" Add-logEntry -message "SyncAction value is: $script:syncAction" Catch { Add-logEntry -messsage "Unable to append session transcription to file: $global:LogFolder\$global:logFileName.TRANSCRIPT.txt" -logLevel 'Error' } Try { $sync = Sync-Objects -objectAction $script:ObjectAction -syncAction $script:SyncAction -MigrationWizCredentials $script:migrationwizCredentials -O365Credentials $script:O365Credentials -Verbose -ErrorAction Stop -errorVariable syncError } Catch { Add-logEntry -message "Fatal Error: $($syncerror.errorrecord.exception) " -logLevel 'Error' } } # end SILENT block # INTERACTIVE mode: set vars and perform sync if ($PSCmdlet.ParameterSetName -eq 'Interactive') { Add-logEntry -message 'INTERACTIVE mode: Running Interactive versions of Get-SyncAction and Get-ObjectAction' Try { $script:ObjectAction = Get-ObjectAction -Interactive -ErrorAction Stop -Verbose Add-logEntry -message "ObjectAction $script:ObjectAction selected" } Catch { throw } Try { $script:SyncAction = Get-SyncAction -Interactive -ErrorAction Stop -Verbose Add-logEntry -message "SyncAction $script:SyncAction selected" } Catch { throw } Try { $sync = Sync-Objects -objectAction $script:ObjectAction -syncAction $script:SyncAction -MigrationWizCredentials $script:migrationwizCredentials -O365Credentials $script:O365Credentials -Verbose -ErrorAction Stop -ErrorVariable syncError } Catch { Add-logEntry -message "Fatal error: $($syncerror.errorrecord.exception)" -logLevel 'Error' } } # end INTERACTIVE block return $sync Enable-SelectMode | Out-Null } # end process block End {} } # end Sync-ADtoO365 |