CT-PS-UserMgmt.psm1

<#
--------------------------------------------------------------------------------------------------

This is a module used internally by CT for managing users on client networks.

Any changes to this module need to be published using the powershell script "Build_CT_Module.ps1"

--------------------------------------------------------------------------------------------------
HOW TO IMPORT INTO SCRIPT:
--------------------------------------------------------------------------------------------------
This module requires the CT-PS-Standard module imported first in the script.
This module should be imported using the commands below (do not copy the asterix's, just whats between).
This will import this module AND initialise the script with all the standard features required by scripts,
including all the log files for each transaction
*****************

Install-Module -Name "CT-PS-UserMgmt" -Force -AllowClobber -Scope CurrentUser -Verbose:$VerbosePreference -ErrorAction Stop
Import-Module -Name "CT-PS-UserMgmt" -Force -Verbose:$VerbosePreference -ErrorAction Stop

*****************

--------------------------------------------------------------------------------------------------


--------------------------------------------------------------------------------------------------
LOG FILES
--------------------------------------------------------------------------------------------------
There are four log files initialised by this module that can be used for output.
You can write to each of these logs accordingly.

$UserMgmt_Log: This is the log for each action performed within this module.

--------------------------------------------------------------------------------------------------





--------------------------------------------------------------------------------------------------
#>


# --------------------------------------------------------------------------------------------------
# The following commands will run when the module is imported


    # Define an object type called "O365skuids"
    # this object uses the SKUIDS from https://learn.microsoft.com/en-us/azure/active-directory/enterprise-users/licensing-service-plan-reference
    # A script is written to grab this csv and output it to notepad so it can be updated here easily. This needs to be run periodically.
    # NB for Lexi: Look at building this into a regex replace during the module publish script in the future

    try{

        #<!-- SKUID START -->
        enum O365skuids
        {
            ADV_COMMS
            ADV_COMMS_TEAMS_ADVCOMMS
            CDSAICAPACITY
            CDSAICAPACITY_CDSAICAPACITY
            CDSAICAPACITY_EXCHANGE_S_FOUNDATION
            SPZA_IW
            SPZA_IW_SPZA
            SPZA_IW_EXCHANGE_S_FOUNDATION
            MCOMEETADV
            WORKPLACE_ANALYTICS_WORKPLACE_ANALYTICS
            WORKPLACE_ANALYTICS_WORKPLACE_ANALYTICS_INSIGHTS_BACKEND
            WORKPLACE_ANALYTICS_WORKPLACE_ANALYTICS_INSIGHTS_USER
        }
        #<!-- SKUID END -->

        # Now create a global variable with these skuids
        $global:O365skuidlist = [Enum]::GetNames([O365skuids]).Trim()

        
        $SelfUpdateScriptBlock = {

            $SKUIDLink = "https://download.microsoft.com/download/e/3/e/e3e9faf2-f28b-490a-9ada-c6089a1fc5b0/Product%20names%20and%20service%20plan%20identifiers%20for%20licensing.csv"

            $env:CTPSUserMgmtSelfUpdate = "True"
            
            $skuidCSV = Invoke-WebRequest -Uri $SKUIDLink -UseBasicParsing # -OutFile "skuids.csv"
            
            $SkuidList = ConvertFrom-Csv $skuidCSV
            
            $skuids = @()
            
            $TotalItems = $SkuidList.Count
            $CurrentItem = 0
            $PercentComplete = 0
            
            $selfmodule = (Get-Module -ListAvailable -Name CT-PS-UserMgmt)[0]

            $FilePath = "$($selfmodule.ModuleBase)\$($selfmodule.RootModule)"
            Write-Host "Updating $($FilePath)"
            
            
            foreach($skuidID in $SkuidList) {
                Write-Progress -Activity "Compiling O365 Skuids" -Status "$PercentComplete% Complete:" -PercentComplete $PercentComplete
                $skuidIDName = $skuidID.String_Id.trim()
                $skuidIDName = $skuidIDName.replace(" ","_")
                $skuidIDName = $skuidIDName.replace("(","")
                $skuidIDName = $skuidIDName.replace(")","")
                $skuidIDName = $skuidIDName.replace("/","")
                $skuidIDName = $skuidIDName.replace("+","")
                $skuidIDName = $skuidIDName.replace("`t","")
            
                $skuidSPN = "$($skuidID.String_Id.trim())_$($skuidID.Service_Plan_Name.trim())" 
                $skuidSPN = $skuidSPN.replace(" ","_")
                $skuidSPN = $skuidSPN.replace("(","")
                $skuidSPN = $skuidSPN.replace(")","")
                $skuidSPN = $skuidSPN.replace("/","")
                $skuidSPN = $skuidSPN.replace("+","")
                $skuidSPN = $skuidSPN.replace("`t","")
            
                if (!($skuids | where-object -FilterScript {$_ -in $skuidIDName})) {
                    $skuids += $skuidIDName
                }
                if (!($skuids | where-object -FilterScript {$_ -in $skuidSPN})) {
                    $skuids += $skuidSPN
                }
                $CurrentItem++
                $PercentComplete = [int](($CurrentItem / $TotalItems) * 100)
            }
            
            
            $skuidreplacement = "#<!-- SKUID START -->`n enum O365skuids`n {`n"
            foreach($skuid in $skuids){
                $skuidreplacement = $skuidreplacement + " $($skuid)`n"
            }
            $skuidreplacement = $skuidreplacement + " }`n #<!-- SKUID END -->`n"
            
            
            $allTheText = [System.Io.File]::ReadAllText($FilePath)
            
            $FindText = $allTheText | select-string '(?smi)#<!-- SKUID START -->[^!]+<!-- SKUID END -->' | %{ $_.Matches } | %{ $_.Value }
            
            $replacedContent = $allTheText.Replace($FindText,$skuidreplacement)
            
            $replacedContent | Set-Content -Path $FilePath

            Write-Host "Updated $($FilePath)"
            
            #Import-Module -Name "CT-PS-UserMgmt" -Scope Local -Verbose:$VerbosePreference -ErrorAction Stop
            
        }
        
        # This self-updates this module with the latest SKUIDS and then re-imports itself ready for use.
        $CTPSUserMgmtSelfUpdate = $env:CTPSUserMgmtSelfUpdate
        If($CTPSUserMgmtSelfUpdate -ne "True") { 
            #$powershellPath = "$env:windir\system32\windowspowershell\v1.0\powershell.exe"
            #$process =
            Write-Output "Self-updating module"
            Invoke-Command -ScriptBlock $SelfUpdateScriptBlock
            Import-Module -Name "CT-PS-UserMgmt" -Scope Local -Verbose:$VerbosePreference -ErrorAction Stop
            #Start-Process $powershellPath -NoNewWindow -ArgumentList ("-ExecutionPolicy Bypass -noninteractive -noprofile " + $SelfUpdateScriptBlock)
            #Update-O365SelfCode -SKUIDLink "https://download.microsoft.com/download/e/3/e/e3e9faf2-f28b-490a-9ada-c6089a1fc5b0/Product%20names%20and%20service%20plan%20identifiers%20for%20licensing.csv"
            #exit
        }
        Write-Output "Module self-updated"
        $env:CTPSUserMgmtSelfUpdate = ""



        $global:UserMgmt_Log = "$CT_DEST\logs\$($Script_Name)\$($DateStamp)_UserMgmt.log"  # The output


        #Define ADUser PSObject Type
        class ADUser
        {
            [ValidateNotNullOrEmpty()][string] $FirstName
            [ValidateNotNullOrEmpty()][string] $LastName
            [ValidateNotNull()][string] $DisplayName = ""
            [AllowNull()][string] $Title
            [AllowNull()][string] $Department
            [AllowNull()][string] $BusinessUnit
            [AllowNull()][string] $HomeDriveLocation
            [AllowNull()][string] $HomeDriveLetter
            [ValidateNotNullOrEmpty()][array] $MembershipGroups
            [ValidateLength(2,2)][string] $DefaultAreaCode = "03"
            [ValidateNotNullOrEmpty()][int] $ExtentionLength
            [AllowNull()][string] $DirectDial
            [AllowNull()][string] $Manager
            [ValidateNotNullOrEmpty()][O365skuids] $Default365LicenseSKU
            [AllowNull()][O365skuids] $Additional365LicenseSKU
            [ValidateNotNullOrEmpty()][string] $TicketNumber
            [AllowNull()][string] $EmailAddress
            [ValidateNotNullOrEmpty()][string] $UserPrincipalName
            [AllowNull()][string] $CompanyName
            [AllowNull()][string] $WWWHomePage
            [AllowNull()][string] $OfficePhone
            [AllowNull()][string] $StreetAddress
            [AllowNull()][string] $City
            [AllowNull()][string] $State
            [AllowNull()][string] $PostalCode
            [AllowNull()][string] $Country
            [AllowNull()][string] $Office
            [AllowNull()][string] $PasswordPlainText
            [ValidateNotNullOrEmpty()][bool] $ChangePasswordAtLogon = $true
            [ValidateNotNullOrEmpty()][string] $OUPath
            [AllowNull()][bool] $MFA
            [AllowNull()][bool] $Hybrid
            [AllowNull()][string] $AccountGUID
        }
    } catch {
        $line = $_.InvocationInfo.ScriptLineNumber
        Write-Error "($($line)): $($_)" -ErrorAction Stop
        Throw
    }

# This ends the script block that run on module import
# --------------------------------------------------------------------------------------------------







# --------------------------------------------------------------------------------------------------
# This function takes an array list of O365 SKUIDs and checks the tenancy to see whether there are enough available,
# reporting back a hashtable of licence counts for each array entry (SKUID, count)

function Search-AvailableOffice365Licenses
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    param(
        [Parameter(Mandatory,ValueFromPipelineByPropertyName,HelpMessage="Please provide the credentials of an Office 365/Azure AD admin account as PSCredential variable type")]
        [pscredential[]] $AADCredentials,
        [Parameter(Mandatory,ValueFromPipelineByPropertyName,HelpMessage="Connection URI for Office 365 / Azure AD")]
        [string] $ConnectionURI, # Array of options to present
        [Parameter(Mandatory,ValueFromPipelineByPropertyName,HelpMessage="SKUIDs to search for as an array")]
        [array] $ProductSKUID
    )

    Process{
        $AvaliableSKUs = Get-MsolAccountSKU -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) | Where-Object {$_.skuPartNumber -eq $ProductSKU}
        
        foreach($item in $AvaliableSKUs)
        {
            $avaliable = $item.ActiveUnits
            $consumed = $item.ConsumedUnits

            if($avaliable -ge $consumed)
            {
                $count = $avaliable - $consumed
                return $count
            }
            elseif($avaliable -eq $consumed)
            {
                return 0
            }
            else
            {
                return -1
            }
        }
    }
}
# --------------------------------------------------------------------------------------------------







# --------------------------------------------------------------------------------------------------
# This is used to create a new user account in Active Directory
# This function must be called from a script running on the AD server of the organisation
Function New-UserAccount {
    [CmdletBinding(SupportsShouldProcess=$true)]
    Param(
        [Parameter(Mandatory,ValueFromPipelineByPropertyName,HelpMessage="Please provide the credentials of a domain admin account as PSCredential variable type")]
        [pscredential[]] $ADCredentials,
        [Parameter(ValueFromPipelineByPropertyName,HelpMessage="Main Contact Name")]
        [string] $MainContact = "Samantha",
        [Parameter(ValueFromPipelineByPropertyName,HelpMessage="Main Contact Email")]
        [string] $MainEmail = "<sjordan@ct.com.au>",
        [Parameter(ValueFromPipelineByPropertyName,HelpMessage="Any emails you want BCC'ed to the onboarding confirmation")]
        [string] $BCCs = "<sjordan@ct.com.au>",
        [Parameter(ValueFromPipelineByPropertyName,HelpMessage="ADFS Server name.")]
        [string] $ADFSServer,
        [Parameter(Mandatory,ValueFromPipelineByPropertyName,HelpMessage="Domain Controller name")]
        [string] $DomainController = $env:COMPUTERNAME,
        [Parameter(HelpMessage="MFA in use?")]
        [switch] $MFA,
        [Parameter(HelpMessage="Hybrid Exchange in use?")]
        [switch] $Hybrid,
        [Parameter(ValueFromPipelineByPropertyName,HelpMessage="Home Drive Location (if any)")]
        [string] $HomeDriveLocation,
        [Parameter(ValueFromPipelineByPropertyName,HelpMessage="Home Drive Letter (if any)")]
        [string] $HomeDriveLetter,
        [Parameter(Mandatory,ValueFromPipelineByPropertyName,HelpMessage="AD Groups to add user to")]
        [array] $DefaultGroups,
        [Parameter(ValueFromPipelineByPropertyName,HelpMessage="Default area code for direct dial numbers (this is incase an 8 digit number is entered)")]
        [string][ValidateLength(2,2)] $DefaultAreaCode = "03",
        [Parameter(ValueFromPipelineByPropertyName,HelpMessage="Extension length (how many numbers for a users extension)")]
        [int] $ExtentionLength,
        [Parameter(Mandatory,ValueFromPipelineByPropertyName,HelpMessage="Office 365 Licence SKU")]
        [ArgumentCompletions('SPB', 'ENTERPRISEPACK','DESKLESSPACK','DESKLESSPACK_GOV','DESKLESSPACK_YAMMER','DESKLESSWOFFPACK','DESKLESSWOFFPACK_GOV','EDUPACK_FACULTY','EDUPACK_STUDENT','ENTERPRISEPACK_FACULTY','ENTERPRISEPACK_FACULTY','ENTERPRISEPACK_STUDENT','ENTERPRISEPACKLRG','ENTERPRISEPACKWITHOUTPROPLUS','ENTERPRISEPACKWSCAL','ENTERPRISEPREMIUM','ENTERPRISEPREMIUM_FACULTY','ENTERPRISEPREMIUM_NOPSTNCONF','ENTERPRISEPREMIUM_OFFICESUBSCRIPTION','ENTERPRISEPREMIUM_STUDENT','ENTERPRISEPREMIUM_STUDENT','ENTERPRISEWITHSCAL_FACULTY','ENTERPRISEWITHSCAL_FACULTY','ENTERPRISEWITHSCAL_STUDENT','LITEPACK','LITEPACK_P2','M365_F1','M365_G3_GOV','M365EDU_A1','M365EDU_A3_FACULTY','M365EDU_A3_STUDENT','M365EDU_A3_STUDENT','M365EDU_A5_FACULTY','M365EDU_A5_FACULTY','M365EDU_A5_FACULTY','MIDSIZEPACK','O365_BUSINESS_ESSENTIALS','O365_BUSINESS_PREMIUM','OFFICE_BASIC','OFFICE_BUSINESS','SMB_BUSINESS_ESSENTIALS','SMB_BUSINESS_PREMIUM','SPE_E3','SPE_E3','SPE_E5','SPE_F1','STANDARD_B_PILOT')]
        [string] $Default365LicenseSKU,
        [Parameter()] [hashtable] $PostData, # The hashtable that needs to be posted to the API
        [Parameter(ValueFromPipelineByPropertyName,HelpMessage="Stores notes to email through on ticket.")] 
        $TicketNotes, # The hashtable that needs to be posted to the API
        [Parameter()] [int] $Retry = 2 # Attempts a retry of the post if it fails
    )
    Process{
        #Do stuff
    }
}
# --------------------------------------------------------------------------------------------------







# --------------------------------------------------------------------------------------------------
# This is used to self-update the O365 class in this module
# A different URL can be passed at any stage
Function Update-O365SelfCode {
    [CmdletBinding(SupportsShouldProcess=$true)]
    Param(
        [Parameter(ValueFromPipelineByPropertyName,HelpMessage="Please provide the URL to grab CSV file from")]
        [ValidateNotNullOrEmpty()][string[]] $SKUIDLink = "https://download.microsoft.com/download/e/3/e/e3e9faf2-f28b-490a-9ada-c6089a1fc5b0/Product%20names%20and%20service%20plan%20identifiers%20for%20licensing.csv"
    )

    Process{
        Start-Process (Get-Process -Id $pid).Path -ArgumentList "-File '$PSScriptRoot\update_skuids.ps1' -NonInteractive" -NoNewWindow
    }
}
    
    
    
# --------------------------------------------------------------------------------------------------