Import File Script Guide
### A.) AD Express mapper helper script
 
- script contains functions which helps user to convert the AD object search results into .csv mapping files
- documentation will explain only the function that is correlated to AD Express mapping for EntraId for Device
 
1. ADConvertToCsv function
- helps organize the search result into .csv list files or .csv mapping files
- called by adding to AD object search pipeline:
 
<AD object search command> | ADConvertToCsv
(AD object search commands are explained in section C.)
 
- will create .csv file to the current script directory from the search result
- if "-Path" is specified, will create file in the path (... | ADConvertToCsv -Path "C:\Users\userx\objects.csv")
- if "-Append" switch is specified, will append existing file (must be called with -Path)
- if "-EntraId" switch is specified, will create mapping file required by ADExpress EntraId for Device
- supports powershell AD(on-prem) and microsoft graph (cloud) search command results
- does not support powershell AzureAD (cloud) search command results, because of deprecated state and insufficient functionality 
 
### B.) INSTRUCTIONS
 
1. prepare powershell window
-for getting AD on-prem objects (section C.1.)
    - available by default
    - any version (https://learn.microsoft.com/en-us/powershell/module/activedirectory/?view=windowsserver2022-ps&source=recommendations)
 
-for getting AD cloud objects via microsoft graph (section C.2.)
    - https://learn.microsoft.com/en-us/powershell/microsoftgraph/installation?view=graph-powershell-1.0
    - powershell version 5.1 and higher (powershell 7 recommended)
    - powershell 7 installation guide https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.4
 
2. Import script to the current powershell session
- run powershell
- move to directory where helper script is located (find directory/folder in UI -> shift-rightmouseclick -> "copy as path"
"cd <directory_path>"
- load script
". .\BT-ADObjectMapper.ps1"
 
3. Create mapping file with source (on-prem) objects filled 
 
-this will create .csv mapping file with source objects filled (on-prem) and empty target objects 
-this requires rights to search through on-prem AD
-function ADConvertToCsv will require switch "-Source" and "-EntraId" to be used
    - "-Source" specifies that we want to create mapping file and fill source objects
    - "-EntraId" specifies that we want to create mapping file for "ADExpress for EntraId"
-use section C.1. to learn about AD on-prem object search commands
-when creating mapping file for DEVICEs, do not continue with step 4. and 5. The mapping file for DEVICEs is final with step 3.
 
-structure:
<AD object search command> |  ADConvertToCsv -Source -EntraId
 
-example:
Get-ADUser -Filter * -SearchBase "OU=Finance,OU=UserAccounts,DC=FABRIKAM,DC=COM"   |   ADConvertToCsv -Source -EntraId
 
 
4. Create object file with target (cloud) objects
 
-this step is required for USERs and GROUPs, not to be used for DEVICEs
-this will create standalone file with target objects that will need to be matched with source objects in next step
-requires rights to search through cloud AD
-function ADConvertToCsv does not require any switch for this step
-use section C.2. to learn about AD cloud object search commands
 
-structure:
<AD cloud object search command> | ADConvertToCsv
 
-example:
Get-MgUser -All | ADConvertToCsv
 
5. Map source objects to target objects
 
-this step is required for USERs and GROUPs, not to be used for DEVICEs
-match objects in the mapping file the way you want
-need to manually map target objects from object file created in step 4 to the source objects in the mapping file created in step 3
-copy respective attributes of object in the mapping file to columns prefixed with "Target"
 
 
### C.) AD Object search commands
 
#C.1. AD (on-premises)
 
C.1.0 Filtering Local objects
-Filter <query>
specifies query string that retrieves AD objects
-SearchBase <path> 
specifies Active Directory path to search under
 
#examples:
Get-ADUser -Filter * -SearchBase "OU=Finance,OU=UserAccounts,DC=FABRIKAM,DC=COM"
-gets all users (specified by -Filter *) in the container "OU=Finance,OU=UserAccounts,DC=FABRIKAM,DC=COM"
 
Get-ADUser -Filter 'SamAccountName -like "*test"' -SearchBase "OU=Finance,OU=UserAccounts,DC=FABRIKAM,DC=COM"
-gets all users, which have SamAccountName ending with "test" in the container "OU=Finance,OU=UserAccounts,DC=FABRIKAM,DC=COM"
 
*more in doc links
 
C.1.1 USER
function:
Get-AdUser (https://learn.microsoft.com/en-us/powershell/module/activedirectory/get-aduser?view=windowsserver2022-ps)
example:
Get-ADUser -Filter * -SearchBase "OU=Finance,OU=UserAccounts,DC=FABRIKAM,DC=COM"| ADConvertToCsv
 
C.1.2 GROUP
function:
Get-ADGroup (https://learn.microsoft.com/en-us/powershell/module/activedirectory/get-adgroup?view=windowsserver2022-ps)
example:
Get-ADGroup -Filter * -SearchBase "OU=Finance,OU=UserAccounts,DC=FABRIKAM,DC=COM" | ADConvertToCsv
 
C.1.3 DEVICE 
function:
Get-ADComputer (https://learn.microsoft.com/en-us/powershell/module/activedirectory/get-adcomputer?view=windowsserver2022-ps)
example:
Get-ADComputer -Filter * -SearchBase "OU=Finance,OU=UserAccounts,DC=FABRIKAM,DC=COM" | ADConvertToCsv
 
 
#C.2 AD (cloud) using Microsoft Graph 
 
C.2.0 Filtering Cloud objects using Microsoft Graph
before calling any function we need to call Connect-MgGraph to connect to the azure entra id (https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.authentication/connect-mggraph?view=graph-powershell-1.0)
 
-All
returns all users
 
-Filter <filter>
allows for OData v3.0 filtering (https://www.odata.org/documentation/odata-version-3-0/odata-version-3-0-core-protocol/#queryingcollections)
 
-Search <phrase>
search items by search phrases
 
-examples:
Get-MgUser -Filter "startsWith(DisplayName, 'a')"
-search users which displayname starts with "a" (case-insensitive)
Get-MgUser -Search '"Mail:Peter"'
-search users which email adress contains "Peter"
 
*more in doc links
 
C.2.1 USER
function:
Get-MgUser (https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.users/get-mguser?view=graph-powershell-1.0&preserve-view=true)
example:
Connect-MgGraph -Scopes 'User.Read.All' (to get permissions and choose entra id- interactive login)
Get-MgUser -All | ADConvertToCsv
 
C.2.2 GROUP
function:
Get-MgGroup (https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.groups/get-mggroup?view=graph-powershell-1.0)
example:
Connect-MgGraph -Scopes 'Group.Read.All' (to get permissions and choose entra id- interactive login)
Get-MgGroup -All | ADConvertToCsv
 
C.2.3 DEVICE
function:
Get-MgDevice (https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.identity.directorymanagement/get-mgdevice?view=graph-powershell-1.0)
example:
Connect-MgGraph -Scopes 'Device.Read.All' (to get permissions and choose entra id- interactive login)
Get-MgDevice -All | ADConvertToCsv
 
*to switch entra id
-disconnect
Disconnect-MgGraph
-connect to a different one
Connect-MgGraph -Scopes <required_scopes>

 

Import File Script
function ADConvertToCsv() #Used to convert AD(onprem/cloud) objects (users,groups,devices/computers) to a .csv list for AD Express mapping file.
{
    Param(
        [Parameter(
            Mandatory=$true, 
            ValueFromPipeline=$true)]
        $InputObject, #Can take input from pipeline
 
        [Parameter(Mandatory=$false)] 
        [string]$Path, #Path to output .csv file, if not provided, output will be created in current ps directory
 
        [Parameter(Mandatory=$false)]
        [switch]$Append, #Appends existing .csv file (-Path must be specified)
 
        [Parameter(Mandatory=$false)]
        [switch]$Source, #To create template mapping file with input as source objects
 
        [Parameter(Mandatory=$false)]
        [switch]$EntraId #To create mapping file for 'EntraID for Device' (can only be used in conjunction with -Source switch)
    )
    Begin
    {
        #append or rewrite
        if ($Append)
        {
            if (!$Path)
            {
                Throw "[ERROR]: Path must be specified (-Path) in order to append existing file."
            }
            elseif (!(Test-Path $Path))
            {
                Throw "[ERROR]: File does not exist. It needs to exist in order to be appended."
            }
 
            $saveOperationType = @{ Append = $true }
        }
        else {
            $saveOperationType = @{ Force = $true }
        }
 
        #specify output path
        $outputPath = ""
        if ($Path)
        {
            $outputPath = $Path
        }
        else
        {
            if ($Source)
            {
                $outputPath = "$PSScriptRoot\object_mapping_file_" + [DateTime]::Now.ToString('yyyy_MM_dd_HH_mm_ss') + ".csv"
            }
            else 
            {
                $outputPath = "$PSScriptRoot\object_list_" + [DateTime]::Now.ToString('yyyy_MM_dd_HH_mm_ss') + ".csv"
            }
        }
 
        if ($EntraId -and (-not $Source))
        {
            Throw "[ERROR]: Switch -EntraId can only be used in conjunction with -Source switch."
        }
 
        $inputList = @()
        $inputType = ""
        $uniqueDomainList = @()
 
    }
    Process
    {
        if ($InputObject)
        {
            #make sure the type is allowed and all objects are of same type
            if ($inputType -eq "")
            {
                $inputType = $InputObject.GetType().FullName
 
                if ($EntraId) #switch only for on-prem objects for 'EntraID for Device'
                {
                    if ($inputType -notin @("Microsoft.ActiveDirectory.Management.ADUser", 
                                        "Microsoft.ActiveDirectory.Management.ADGroup", 
                                        "Microsoft.ActiveDirectory.Management.ADComputer"))
                    {
                        Throw "[ERROR]: Type $inputType is not one of the accepted types."
                    }
                }
                else 
                {
                    if ($inputType -notin @("Microsoft.ActiveDirectory.Management.ADUser", 
                                            "Microsoft.ActiveDirectory.Management.ADGroup", 
                                            "Microsoft.ActiveDirectory.Management.ADComputer", 
                                            "Microsoft.Graph.PowerShell.Models.MicrosoftGraphUser",
                                            "Microsoft.Graph.PowerShell.Models.MicrosoftGraphGroup",
                                            "Microsoft.Graph.PowerShell.Models.MicrosoftGraphDevice",
                                            "Microsoft.Open.AzureAD.Model.User", 
                                            "Microsoft.Open.AzureAD.Model.Group", 
                                            "Microsoft.Open.AzureAD.Model.Device"))
                    {
                        Throw "[ERROR]: Type $inputType is not one of the accepted types."
                    }
                }
            }
            elseif ($InputObject.GetType().FullName -ne $inputType)
            {
                Throw "[ERROR]: Type $($InputObject.GetType().FullName) is not the same type as previous $inputType."
            }
 
            if ($EntraId) #get DC from distinguished name and get domain info
            {
                $dnList = $InputObject.DistinguishedName -split '(?<!\\),' | Where-Object { $_ -like "DC*" }
                $dnDC = $dnList -join ","
 
                if ($uniqueDomainList.DNPath -notcontains $dnDC)
                {
                    $result = Get-ADDomain -Identity $dnDC
                    if (-not $result)
                    {
                        Throw "[ERROR]: Domain not found for object " + $InputObject.Name
                    }
 
                    $domainObject = [PSCustomObject]@{
                        DNPath = $dnDC
                        NetBIOSName = $result.NetBIOSName
                        DomainName = $result.Name
                    }
 
                    $uniqueDomainList += $domainObject
                }
                else
                {
                    $domainObject = $uniqueDomainList | Where-Object {$_.DNPath -eq $dnDC}
                }
 
                $InputObject | Add-Member -NotePropertyName NetBIOSName -NotePropertyValue $domainObject.NetBIOSName -Force
                $InputObject | Add-Member -NotePropertyName DomainName -NotePropertyValue $domainObject.DomainName -Force
            }
 
            $inputList += $InputObject
        }
    }
    End 
    {
 
        if ($inputList.Count -eq 0)
        {
            Write-Host "[INFO]: There are no records. Csv file will not be created."
            return
        }
 
        #transform input into .csv file
        switch ($inputType)
        {
            "Microsoft.ActiveDirectory.Management.ADUser" 
            {
                if ($Source)
                {
                    if ($EntraId)
                    {
                        $inputList | 
                            Select-Object -Property @{Name="SourceObjectId"; Expression={$_.ObjectGUID}}, `
                                @{Name="SourceUserPrincipalName"; Expression={$_.UserPrincipalName}}, `
                                @{Name="SourceSamAccountName"; Expression={$_.SamAccountName}}, `
                                @{Name="SourceNetBIOSName"; Expression={$_.NetBIOSName}}, `
                                @{Name="SourceDomainName"; Expression={$_.DomainName}}, `
                                @{Name="SourceObjectSID"; Expression={$_.SID}}, `
                                @{Name="TargetObjectId"; Expression={""}}, `
                                @{Name="TargetUserPrincipalName"; Expression={""}}, `
                                @{Name="TargetSamAccountName"; Expression={""}}, `
                                @{Name="Comments (Optional)"; Expression={""}} |
                                    Export-Csv -Path $outputPath -Delimiter "," -NoTypeInformation @saveOperationType
                    }
                    else
                    {
                        $inputList | 
                            Select-Object -Property @{Name="SourceObjectId"; Expression={$_.ObjectGUID}}, `
                                @{Name="SourceUserPrincipalName (Optional)"; Expression={$_.UserPrincipalName}}, `
                                @{Name="SourceSamAccountName (Optional)"; Expression={$_.SamAccountName}}, `
                                @{Name="TargetObjectId"; Expression={""}}, `
                                @{Name="TargetUserPrincipalName (Optional)"; Expression={""}}, `
                                @{Name="TargetSamAccountName (Optional)"; Expression={""}}, `
                                @{Name="Comments (Optional)"; Expression={""}} |
                                    Export-Csv -Path $outputPath -Delimiter "," -NoTypeInformation @saveOperationType
                    }
                }
                else 
                {
                    $inputList | 
                        Select-Object -Property @{Name="ObjectId"; Expression={$_.ObjectGUID}}, `
                            @{Name="UserPrincipalName"; Expression={$_.UserPrincipalName}}, `
                            @{Name="SamAccountName"; Expression={$_.SamAccountName}} | 
                                Export-Csv -Path $outputPath -Delimiter "," -NoTypeInformation @saveOperationType
                }
            }
 
            "Microsoft.ActiveDirectory.Management.ADGroup" 
            {
                if ($Source)
                {
                    if ($EntraId)
                    {
                        $inputList | 
                            Select-Object -Property @{Name="SourceObjectId"; Expression={$_.ObjectGUID}}, `
                                @{Name="SourceSamAccountName"; Expression={$_.SamAccountName}}, `
                                @{Name="SourceNetBIOSName"; Expression={$_.NetBIOSName}}, `
                                @{Name="SourceDomainName"; Expression={$_.DomainName}}, `
                                @{Name="SourceObjectSID"; Expression={$_.SID}}, `
                                @{Name="TargetObjectId"; Expression={""}}, `
                                @{Name="TargetSamAccountName"; Expression={""}}, `
                                @{Name="Comments (Optional)"; Expression={""}} |
                                    Export-Csv -Path $outputPath -Delimiter "," -NoTypeInformation @saveOperationType
                    }
                    else
                    {
                        $inputList | 
                            Select-Object -Property @{Name="SourceObjectId"; Expression={$_.ObjectGUID}}, `
                                @{Name="SourceSamAccountName (Optional)"; Expression={$_.SamAccountName}}, `
                                @{Name="TargetObjectId"; Expression={""}}, `
                                @{Name="TargetSamAccountName (Optional)"; Expression={""}}, `
                                @{Name="Comments (Optional)"; Expression={""}} |
                                    Export-Csv -Path $outputPath -Delimiter "," -NoTypeInformation @saveOperationType
                    }
                }
                else 
                {
                    $inputList | 
                        Select-Object -Property @{Name="ObjectId"; Expression={$_.ObjectGUID}}, `
                            @{Name="SamAccountName"; Expression={$_.SamAccountName}} | 
                                Export-Csv -Path $outputPath -Delimiter "," -NoTypeInformation @saveOperationType
                }
            }
            "Microsoft.ActiveDirectory.Management.ADComputer" {
                if ($Source)
                {
                    if ($EntraId)
                    {
                        $inputList | 
                            Select-Object -Property @{Name="SourceObjectId"; Expression={$_.ObjectGUID}}, `
                                @{Name="SourceSamAccountName"; Expression={$_.SamAccountName}}, `
                                @{Name="SourceDomainName"; Expression={$_.DomainName}}, `
                                @{Name="Comments (Optional)"; Expression={""}} | 
                                    Export-Csv -Path $outputPath -Delimiter "," -NoTypeInformation @saveOperationType
                    }
                    else
                    {
                        $inputList | 
                            Select-Object -Property @{Name="SourceObjectId"; Expression={$_.ObjectGUID}}, `
                                @{Name="SourceSamAccountName (Optional)"; Expression={$_.SamAccountName}}, `
                                @{Name="Comments (Optional)"; Expression={""}} | 
                                    Export-Csv -Path $outputPath -Delimiter "," -NoTypeInformation @saveOperationType
                    }
                }
                else 
                {
                    $inputList | 
                        Select-Object -Property @{Name="ObjectId"; Expression={$_.ObjectGUID}}, `
                            @{Name="SamAccountName"; Expression={$_.SamAccountName}} | 
                                Export-Csv -Path $outputPath -Delimiter "," -NoTypeInformation @saveOperationType
                }
            }
            "Microsoft.Graph.PowerShell.Models.MicrosoftGraphUser"{
                if ($Source)
                {
                    $inputList | 
                        Select-Object -Property @{Name="SourceObjectId"; Expression={$_.Id}}, `
                            @{Name="SourceUserPrincipalName (Optional)"; Expression={$_.UserPrincipalName}}, `
                            @{Name="SourceSamAccountName (Optional)"; Expression={$_.OnPremisesSamAccountName}}, `
                            @{Name="TargetObjectId"; Expression={""}}, `
                            @{Name="TargetUserPrincipalName (Optional)"; Expression={""}}, `
                            @{Name="TargetSamAccountName (Optional)"; Expression={""}}, `
                            @{Name="Comments (Optional)"; Expression={""}} |
                                Export-Csv -Path $outputPath -Delimiter "," -NoTypeInformation @saveOperationType
                }
                else 
                {
                    $inputList | 
                        Select-Object -Property @{Name="ObjectId"; Expression={$_.Id}}, `
                            @{Name="UserPrincipalName"; Expression={$_.UserPrincipalName}}, `
                            @{Name="SamAccountName"; Expression={$_.OnPremisesSamAccountName}} | 
                                Export-Csv -Path $outputPath -Delimiter "," -NoTypeInformation @saveOperationType
                }
            }
            "Microsoft.Graph.PowerShell.Models.MicrosoftGraphGroup"{
                if ($Source)
                {
                    $inputList | 
                        Select-Object -Property @{Name="SourceObjectId"; Expression={$_.Id}}, `
                            @{Name="SourceSamAccountName (Optional)"; Expression={$_.OnPremisesSamAccountName}}, `
                            @{Name="TargetObjectId"; Expression={""}}, `
                            @{Name="TargetSamAccountName (Optional)"; Expression={""}}, `
                            @{Name="Comments (Optional)"; Expression={""}} |
                                Export-Csv -Path $outputPath -Delimiter "," -NoTypeInformation @saveOperationType
                }
                else 
                {
                    $inputList | 
                        Select-Object -Property @{Name="ObjectId"; Expression={$_.Id}}, `
                            @{Name="SamAccountName"; Expression={$_.OnPremisesSamAccountName}}, `
                            @{Name="DisplayName"; Expression={$_.DisplayName}}, `
                            @{Name="EmailAddress"; Expression={$_.Mail}} | 
                                Export-Csv -Path $outputPath -Delimiter "," -NoTypeInformation @saveOperationType
                }
            }
            "Microsoft.Graph.PowerShell.Models.MicrosoftGraphDevice"{
                if ($Source)
                {
                    $inputList | 
                        Select-Object -Property @{Name="SourceObjectId"; Expression={$_.Id}}, `
                            @{Name="SourceSamAccountName (Optional)"; Expression={$_.OnPremisesSamAccountName}}, `
                            @{Name="Comments (Optional)"; Expression={""}} | 
                                Export-Csv -Path $outputPath -Delimiter "," -NoTypeInformation @saveOperationType
                }
                else 
                {
                    $inputList | 
                        Select-Object -Property @{Name="ObjectId"; Expression={$_.Id}}, `
                            @{Name="SamAccountName"; Expression={$_.OnPremisesSamAccountName}}, `
                            @{Name="DisplayName"; Expression={$_.DisplayName}} | 
                                Export-Csv -Path $outputPath -Delimiter "," -NoTypeInformation @saveOperationType
                }
            }
            "Microsoft.Open.AzureAD.Model.User" {
                if ($Source)
                {
                    $inputList | 
                        Select-Object -Property @{Name="SourceObjectId"; Expression={$_.ObjectId}}, `
                            @{Name="SourceUserPrincipalName (Optional)"; Expression={$_.UserPrincipalName}}, `
                            @{Name="SourceSamAccountName (Optional)"; Expression={""}}, `
                            @{Name="TargetObjectId"; Expression={""}}, `
                            @{Name="TargetUserPrincipalName (Optional)"; Expression={""}}, `
                            @{Name="TargetSamAccountName (Optional)"; Expression={""}}, `
                            @{Name="Comments (Optional)"; Expression={""}} |
                                Export-Csv -Path $outputPath -Delimiter "," -NoTypeInformation @saveOperationType
                }
                else 
                {
                    $inputList | 
                        Select-Object -Property @{Name="ObjectId"; Expression={$_.ObjectId}}, `
                            @{Name="UserPrincipalName"; Expression={$_.UserPrincipalName}}, `
                            @{Name="SamAccountName"; Expression={""}} | 
                                Export-Csv -Path $outputPath -Delimiter "," -NoTypeInformation @saveOperationType
                }
            }
            "Microsoft.Open.AzureAD.Model.Group" {
                if ($Source)
                {
                    $inputList | 
                        Select-Object -Property @{Name="SourceObjectId"; Expression={$_.ObjectId}}, `
                            @{Name="SourceSamAccountName (Optional)"; Expression={""}}, `
                            @{Name="TargetObjectId"; Expression={""}}, `
                            @{Name="TargetSamAccountName (Optional)"; Expression={""}}, `
                            @{Name="Comments (Optional)"; Expression={""}} |
                                Export-Csv -Path $outputPath -Delimiter "," -NoTypeInformation @saveOperationType
                }
                else 
                {
                    $inputList | 
                        Select-Object -Property @{Name="ObjectId"; Expression={$_.ObjectId}}, `
                            @{Name="SamAccountName"; Expression={""}}, `
                            @{Name="DisplayName"; Expression={$_.DisplayName}}, `
                            @{Name="EmailAddress"; Expression={$_.Mail}} | 
                                Export-Csv -Path $outputPath -Delimiter "," -NoTypeInformation @saveOperationType
                }
            }
            "Microsoft.Open.AzureAD.Model.Device" {
                if ($Source)
                {
                    $inputList | 
                        Select-Object -Property @{Name="SourceObjectId"; Expression={$_.ObjectId}}, `
                            @{Name="SourceSamAccountName (Optional)"; Expression={""}}, `
                            @{Name="Comments (Optional)"; Expression={""}} | 
                                Export-Csv -Path $outputPath -Delimiter "," -NoTypeInformation @saveOperationType
                }
                else 
                {
                    $inputList | 
                        Select-Object -Property @{Name="ObjectId"; Expression={$_.ObjectId}}, `
                            @{Name="SamAccountName"; Expression={""}}, `
                            @{Name="DisplayName"; Expression={$_.DisplayName}} | 
                                Export-Csv -Path $outputPath -Delimiter "," -NoTypeInformation @saveOperationType
                }
            }
            default {
                Throw "[ERROR]: Type $inputType is not one of the accepted types."
            }
        }
        
        if ($Append)
        {
            Write-Host "[INFO]: File '$($outputPath)' appended"
        }
        else
        {
            Write-Host "[INFO]: File '$($outputPath)' created"
        }
    }
}
 
 
function ADMapLocalUsers($sourceUsersPath, $targetUsersPath) #Map two user files based on SamAccountName
{
    $sourceUsers = Import-Csv -Path $sourceUsersPath -Delimiter ","
    $targetUsers = Import-Csv -Path $targetUsersPath -Delimiter ","
    $mappedUsers = @()
    $nonMappedUsers = @()
 
    #for each source user, try to find target user via SamAccountName
    $sourceUsers | % {
        $sourceObject = $_
        $targetMatch = $null
        if ($sourceObject.SamAccountName -ne "")
        {
            $targetMatch = $targetUsers | Where-Object {$_.SamAccountName -eq $sourceObject.SamAccountName}
        }
        
        if ($targetMatch) #if found, create match record
        {
            if ($targetMatch.Count -gt 1)
            {
                throw "[ERROR]: Target User SamAccountName is not unique '$($sourceObject.SamAccountName)'"
            }
 
            $mappedUsers += [PSCustomObject]@{
                SourceObjectId = $sourceObject.ObjectId
                SourceUserPrincipalName = $sourceObject.UserPrincipalName
                SourceSamAccountName = $sourceObject.SamAccountName
                TargetObjectId = $targetMatch.ObjectId
                TargetUserPrincipalName = $targetMatch.UserPrincipalName
                TargetSamAccountName = $targetMatch.SamAccountName
                Comments = ""
            }
        }
        else #if havent found, create record without 
        {
            $nonMappedUsers += [PSCustomObject]@{
                SourceObjectId = $sourceObject.ObjectId
                SourceUserPrincipalName = $sourceObject.UserPrincipalName
                SourceSamAccountName = $sourceObject.SamAccountName
                TargetObjectId = ""
                TargetUserPrincipalName = ""
                TargetSamAccountName = ""
                Comments = ""
            }
        }
    }
 
    #save target users which dont have source user match
    $sourceUsersWithSam = $sourceUsers | Where-Object {$_.SamAccountName -ne ""} 
    $targetUsersWithoutSourceSam = $targetUsers | Where-Object { $_.SamAccountName -notin $sourceUsersWithSam.SamAccountName}
    $targetUsersWithoutSourceSam | % {
        $nonMappedUsers += [PSCustomObject]@{
            SourceObjectId = ""
            SourceUserPrincipalName = ""
            SourceSamAccountName = ""
            TargetObjectId = $_.ObjectId
            TargetUserPrincipalName = $_.UserPrincipalName
            TargetSamAccountName = $_.SamAccountName
            Comments = ""
        }
    }
 
    #save
    $mappedUsers += $nonMappedUsers
    $outputPath = "$PSScriptRoot\user_map_" + [DateTime]::UtcNow.ToString('yyyy_MM_dd_HH_mm_ss') + ".csv"
    $mappedUsers | Export-Csv -Path $outputPath -Delimiter "," -NoTypeInformation
    Write-Host "[INFO]: File '$($outputPath)' created"
}
function ADMapLocalGroups($sourceGroupsPath, $targetGroupsPath) #Map two group files based on SamAccountName
{
    $sourceGroups = Import-Csv -Path $sourceGroupsPath -Delimiter ","
    $targetGroups = Import-Csv -Path $targetGroupsPath -Delimiter ","
    $mappedGroups = @()
    $nonMappedGroups = @()
 
    #for each source group, try to find target group via SamAccountName
    $sourceGroups | % {
        $sourceObject = $_
        $targetMatch = $null
        if ($sourceObject.SamAccountName -ne "")
        {
            $targetMatch = $targetGroups | Where-Object {$_.SamAccountName -eq $sourceObject.SamAccountName}
        }
        
        if ($targetMatch) #if found, create match record
        {
            if ($targetMatch.Count -gt 1)
            {
                throw "Target Group SamAccountName is not unique '$($sourceObject.SamAccountName)'"
            }
 
            $mappedGroups += [PSCustomObject]@{
                SourceObjectId = $sourceObject.ObjectId
                SourceSamAccountName = $sourceObject.SamAccountName
                TargetObjectId = $targetMatch.ObjectId
                TargetSamAccountName = $targetMatch.SamAccountName
                Comments = ""
            }
        }
        else #if havent found, create record without 
        {
            $nonMappedGroups += [PSCustomObject]@{
                SourceObjectId = $sourceObject.ObjectId
                SourceSamAccountName = $sourceObject.SamAccountName
                TargetObjectId = ""
                TargetSamAccountName = ""
                Comments = ""
            }
        }
    }
 
    #save target groups which dont have source group match
    $sourceGroupsWithSam = $sourceGroups | Where-Object {$_.SamAccountName -ne ""} 
    $targetGroupsWithoutSourceSam = $targetGroups | Where-Object { $_.SamAccountName -notin $sourceGroupsWithSam.SamAccountName}
    $targetGroupsWithoutSourceSam | % {
        $nonMappedGroups += [PSCustomObject]@{
            SourceObjectId = ""
            SourceSamAccountName = ""
            TargetObjectId = $_.ObjectId
            TargetSamAccountName = $_.SamAccountName
            Comments = ""
        }
    }
 
    #save
    $mappedGroups += $nonMappedGroups
    $outputPath = "$PSScriptRoot\group_map_" + [DateTime]::UtcNow.ToString('yyyy_MM_dd_HH_mm_ss') + ".csv"
    $mappedGroups | Export-Csv -Path $outputPath -Delimiter "," -NoTypeInformation
    Write-Host "[INFO]: File '$($outputPath)' created"
}