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