public/Revoke-UcmOffice365UserLicence.ps1

#PerformScriptSigning
Function Revoke-UcmOffice365UserLicence
{
    <#
            .SYNOPSIS
            Revokes a licence to an Office365 user
 
            .DESCRIPTION
            Function will check for revoke the requested licence from the supplied user
 
            .EXAMPLE
            PS> Revoke-UcmOffice365UserLicence -upn 'button.mash@contoso.com' -LicenceType 'MCOEV'
            Grants the Microsoft Phone System Licence to the user Button Mash
 
            .INPUTS
            This function accepts both parameter and pipline input
 
            .OUTPUT
            This Cmdet returns a PSCustomObject with multiple Keys to indicate status
            $Return.Status
            $Return.Message
 
            Return.Status can return one of four values
            "OK" : The licence has been removed.
            "Warn" : The licence was not assigned to the user, no changes have been made
            "Error" : Unable to revoke licence, check $return.message for more information
            "Unknown" : Cmdlet reached the end of the fucntion without returning anything, this shouldnt happen, if it does please log an issue on Github
 
            Return.Message returns descriptive text based on the outcome, mainly for logging or reporting
 
            .NOTES
            Version: 1.1
            Date: 03/04/2021
 
            .VERSION HISTORY
            1.1: Updated to "Ucm" naming convention
            1.0: Initial Public Release
 
            .REQUIRED FUNCTIONS/MODULES
            Modules
            AzureAD (Install-Module AzureAD)
            MSOnline (Install-Module MSOnline)
            UcmPSTools (Install-Module UcmPsTools) Includes Cmdlets below.
 
            Cmdlets
            Write-UcmLog: https://github.com/Atreidae/UcmPSTools/blob/main/public/Write-UcmLog.ps1
            Test-UcmMSOLConnection: https://github.com/Atreidae/UcmPSTools/blob/main/public/Test-UcmMSOLConnection.ps1
            New-UcmMSOLConnection: https://github.com/Atreidae/UcmPSTools/blob/main/public/New-UcmMSOLConnection.ps1
            Write-UcmHTMLReport: https://github.com/Atreidae/UcmPSTools/blob/main/public/Write-UcmHTMLReport.ps1
 
            .REQUIRED PERMISSIONS
            'Office365 User Admin' or better
 
            .LINK
            https://www.UcMadScientist.com
            https://github.com/Atreidae/UcmPSTools
 
            .ACKNOWLEDGEMENTS
            Assign licenses for specific services in Office 365 using PowerShell:
 
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '', Scope='Function')] #Todo, update return variable to return an array of pipeline objects, so we can report the status of each one to the calling function

    Param
    (
        [Parameter(ValueFromPipelineByPropertyName=$true, Mandatory, Position=1,HelpMessage='The UPN of the user you wish to revoke the licence from, eg: button.mash@contoso.com')] [string]$UPN,
        [Parameter(ValueFromPipelineByPropertyName=$true, Mandatory, Position=2,HelpMessage='The licence you wish to revoke, eg: MCOEV')] [string]$LicenceType
    )

    #region functionSetup, Set Default Variables for HTML Reporting and Write Log
    $Function = 'Revoke-UcmOffice365UserLicence'
    [hashtable]$Return = @{}
    $return.function = $Function
    $return.Status = "Unknown"
    $return.Message = "Function did not return a status message"

    # Log why we were called
    Write-UcmLog -Message "$($MyInvocation.InvocationName) called with $($MyInvocation.Line)" -Severity 1 -Component $function
    Write-UcmLog -Message "Parameters" -Severity 1 -Component $function -LogOnly
    Write-UcmLog -Message "$($PsBoundParameters.Keys)" -Severity 1 -Component $function -LogOnly
    Write-UcmLog -Message "Parameters Values" -Severity 1 -Component $function -LogOnly
    Write-UcmLog -Message "$($PsBoundParameters.Values)" -Severity 1 -Component $function -LogOnly
    Write-UcmLog -Message "Optional Arguments" -Severity 1 -Component $function -LogOnly
    Write-UcmLog -Message "$Args" -Severity 1 -Component $function -LogOnly

    #endregion functionSetup

    #region functionWork

    #Check to see if we are connected to MSOL
    $Test = (Test-UcmMSOLConnection -Reconnect)
    If ($Test.Status -ne "OK")
    {
        #MSOL check failed, return an error.
        Write-UcmLog -Message "Something went wrong granting $UPN's licence" -Severity 3 -Component $function
        Write-UcmLog -Message "Test-UcmMSOLConnection could not locate an MSOL connection" -Severity 2 -Component $function
        $Return.Status = "Error"
        $Return.Message = "No MSOL Connection"
        Return $Return
    }

    #Get the details of exisiting licences to build licence names
    Write-UcmLog -Message "Building Licence Prefix" -Severity 2 -Component $function
    Try
    {
        #Office365 does this thing where it preprends the tenant name on the licence
        #For example PHONESYSTEM_VIRTUALUSER would be "contoso:PHONESYSTEM_VIRTUALUSER"
        #So we need to learn the prefix, we do this by looking for the licence and storing it
        $O365AcctSku = $null
        $O365AcctSku = Get-MsolAccountSku | Where-Object {$_.SkuPartNumber -like $LicenceType}

        #Using the stored details, build the full licence name
        $LicenceToRevoke = "$($O365AcctSku.AccountName):$LicenceType"
    }

    Catch
    {
        #We couldnt get the licence details, it could be a permissions issue or the connection might be broken. Return an error.
        Write-UcmLog -Message "Error Running Get-MsolAccountSku" -Severity 3 -Component $function
        Write-UcmLog -Message $error[0]  -Severity 3 -Component $function
        $Return.Status = "Error"
        $Return.Message = "Unable to run Get-MsolAccountSku to obtain tenant prefix"
        Return $Return
    }

    If ($null -eq $O365AcctSku)
    {
        #The licence requested doesnt exist on the tenant, return an error
        Write-UcmLog -Message "Unable to locate Licence on Tenant" -Severity 3 -Component $function
        $Return.Status = "Error"
        $Return.Message = "Unable to locate $LicenceType Licence"
        Return $Return
    }

    #We have built the licence details, check to see if the specified user exists.
    Try
    {
        Write-UcmLog -Message "Checking for Existing User $UPN ..." -Severity 2 -Component $function
        $O365User = (Get-MsolUser -UserPrincipalName $UPN -ErrorAction Stop)
        Write-UcmLog -Message "User Exists. checking licences..." -Severity 2 -Component $function

        #Found the user, check to see if they have the licence.
        If ($O365User.Licenses.accountSkuID -Notcontains $LicenceToRevoke)
        {
            #Looks like the user doesnt have that licence, skip and return a warning.
            Write-UcmLog -Message "User doesnt have that licence, Skipping" -Severity 3 -Component $function
            $Return.Status = "Warning"
            $Return.Message = "Skipped: Not Licenced"
            Return $Return
        }

        #User has the licence, try revoking licence from the user
        Try
        {
            Write-UcmLog -Message "User has licence, Revoke Licence" -Severity 2 -Component $function
            #Try Removing the licence
            [Void] (Set-MsolUserLicense -UserPrincipalName $UPN -RemoveLicenses $LicenceToRevoke -ErrorAction stop)
            Write-UcmLog -Message "Licence Revoked" -Severity 2 -Component $function

            #Everything went well, return OK
            $Return.Status = "OK"
            $Return.Message = "Licence Revoked"
            Return $Return
        }
        #Something went wrong removing the licence.
        Catch
        {
            #Return an error
            Write-UcmLog -Message "Something went wrong removing the licence from user $UPN" -Severity 3 -Component $function
            Write-UcmLog -Message $Error[0] -Severity 3 -Component $function
            $Return.Status = "Error"
            $Return.Message = $Error[0]
            Return $Return
        }
    } #End User Check try block #Todo, split this up into seperate blocks to minimise nesting.

    #User doesnt exist, return an error message
    Catch
    {
        #Return an error
        Write-UcmLog -Message "Something went wrong revoking $UPN's licence" -Severity 3 -Component $function
        Write-UcmLog -Message "Could not locate user $UPN" -Severity 2 -Component $function
        $Return.Status = "Error"
        $Return.Message = "User Not Found"
        Return $Return
    }
    #endregion FunctionWork


    #region functionReturn

    #Default Return Variable for my HTML Reporting Fucntion
    Write-UcmLog -Message "Reached end of $function without a Return Statement" -Severity 3 -Component $function
    $return.Status = "Unknown"
    $return.Message = "Write-UcmLog did not encounter return statement"
    Return $Return
    #endregion functionReturn
}

# SIG # Begin signature block
# MIIRwgYJKoZIhvcNAQcCoIIRszCCEa8CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUrTqUhu6UaBZcxHsvo7aX7X+V
# cNaggg4OMIIGsDCCBJigAwIBAgIQCK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0B
# AQwFADBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD
# VQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVk
# IFJvb3QgRzQwHhcNMjEwNDI5MDAwMDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYD
# VQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lD
# ZXJ0IFRydXN0ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEg
# Q0ExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5
# WRuxiEL1M4zrPYGXcMW7xIUmMJ+kjmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJP
# DqFX/IiZwZHMgQM+TXAkZLON4gh9NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXz
# ENOLsvsI8IrgnQnAZaf6mIBJNYc9URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bq
# HPNlaJGiTUyCEUhSaN4QvRRXXegYE2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTC
# fMjqGzLmysL0p6MDDnSlrzm2q2AS4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaD
# G7dqZy3SvUQakhCBj7A7CdfHmzJawv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urO
# kfW+0/tvk2E0XLyTRSiDNipmKF+wc86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7AD
# K5GyNnm+960IHnWmZcy740hQ83eRGv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4
# R+Z1MI3sMJN2FKZbS110YU0/EpF23r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlN
# Wdt4z4FKPkBHX8mBUHOFECMhWWCKZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0I
# U0F8WD1Hs/q27IwyCQLMbDwMVhECAwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYB
# Af8CAQAwHQYDVR0OBBYEFGg34Ou2O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaA
# FOzX44LScV1kTN8uZz/nupiuHA9PMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAK
# BggrBgEFBQcDAzB3BggrBgEFBQcBAQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9v
# Y3NwLmRpZ2ljZXJ0LmNvbTBBBggrBgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4
# oDagNIYyaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJv
# b3RHNC5jcmwwHAYDVR0gBBUwEzAHBgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcN
# AQEMBQADggIBADojRD2NCHbuj7w6mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcT
# Ep6QRJ9L/Z6jfCbVN7w6XUhtldU/SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WT
# auPrINHVUHmImoqKwba9oUgYftzYgBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9
# ntSZz0rdKOtfJqGVWEjVGv7XJz/9kNF2ht0csGBc8w2o7uCJob054ThO2m67Np37
# 5SFTWsPK6Wrxoj7bQ7gzyE84FJKZ9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0
# HKKlS43Nb3Y3LIU/Gs4m6Ri+kAewQ3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL
# 6TEa/y4ZXDlx4b6cpwoG1iZnt5LmTl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+1
# 6oh7cGvmoLr9Oj9FpsToFpFSi0HASIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8
# M4+uKIw8y4+ICw2/O/TOHnuO77Xry7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrF
# hsP2JjMMB0ug0wcCampAMEhLNKhRILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy
# 1lKQ/a+FSCH5Vzu0nAPthkX0tGFuv2jiJmCG6sivqf6UHedjGzqGVnhOMIIHVjCC
# BT6gAwIBAgIQDyLHeeRvkUFg5QtSFTT8FjANBgkqhkiG9w0BAQsFADBpMQswCQYD
# VQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lD
# ZXJ0IFRydXN0ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEg
# Q0ExMB4XDTIzMDIyMzAwMDAwMFoXDTI2MDIyMzIzNTk1OVowXjELMAkGA1UEBhMC
# QVUxETAPBgNVBAgTCFZpY3RvcmlhMRAwDgYDVQQHEwdCZXJ3aWNrMRQwEgYDVQQK
# EwtKYW1lcyBBcmJlcjEUMBIGA1UEAxMLSmFtZXMgQXJiZXIwggIiMA0GCSqGSIb3
# DQEBAQUAA4ICDwAwggIKAoICAQC47oExh25TrxvApIYdMRYvjOdZCb8WwgeTemm3
# ZY7BElIWu6+gzRGqQe8RFsN7oIgin5pvjTYIToxt1CCag2A5o8L0NtULmxJEegc+
# VaF24DZQqI4qGQGH/Qnglqys6+yPkwLnfeSxpeWe4u49HUUGDFIxHCh42MlCLp/f
# fHT49QhhpO+LyeLnDoUs6DmahyIb6NeE2cW5AYRXEesW7GRNfXzygBSlVWJOgvcy
# V5Y4IvAZVx2hKKMTjYFIz4/RYMg7fwYZEJ2LRJ/GnVazobKAvh6ZBet5KwVNI9EI
# 29DtWQyK/RoPOguTRcB5VuiZVlv0xjBYM7iJuH2Soa3StQYVxL/5gjZCC9WOs4NR
# EIGU3XmHoogFDvoT1vf1izMPFQzdZfgPvy/XXsbgTVo5ncesJ6WtZwqwCXG1K0XW
# IPZqTHolc1MyU6K1bEHO+7YWLpKgM9THl644G7PEhcKpNDsHlfvLVQdYhI55UJtc
# iyMrTw11CNECvk3GK1mrluvKsrxdaH6G3Sp9VVHRtef6OZ5SlzkM5ID4egB2bXRb
# R/69bEuZr5hhm+v2lBSWIbZj/Mva6i/a/TAvy4vvPLo3DRcASkYZDC4T8gDMzmpG
# Xs4jAc9sfTL9z+o5u1PLJHFGRjJ+Wa2CgSftCdbKLjn+AY9m8ipc8jmOBKNY9yGI
# pQWapQIDAQABo4ICAzCCAf8wHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0
# TkIwHQYDVR0OBBYEFOBsg1xudlbXVSql8pWbiHoTyZS/MA4GA1UdDwEB/wQEAwIH
# gDATBgNVHSUEDDAKBggrBgEFBQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1odHRw
# Oi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmlu
# Z1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNybDBToFGgT4ZNaHR0cDovL2NybDQuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hB
# Mzg0MjAyMUNBMS5jcmwwPgYDVR0gBDcwNTAzBgZngQwBBAEwKTAnBggrBgEFBQcC
# ARYbaHR0cDovL3d3dy5kaWdpY2VydC5jb20vQ1BTMIGUBggrBgEFBQcBAQSBhzCB
# hDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUF
# BzAChlBodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVk
# RzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAA
# MA0GCSqGSIb3DQEBCwUAA4ICAQBOh5vRXqTCQzv0T1F2EgDkq5VnljlHsa7Ov/d/
# lpOvr8NebBiatxbhfhPCnToY7BD2f7YVsUuQ+VDdcIYsskcU5spBHcFYidg2jGu4
# 59FGMaS765XStDwGGTN/360gEsNYSnKWYL4+8jYWHlzRO0jHloyWz+gF5dYWzdDJ
# u1dudLIJ0RgrEVJeLSgIBWygLL5EyIzOPlrxztsILMSbdPTQLeBIm7ipOk4EACx1
# hhBVUsUoCAlASH+yCKDU4v2HFd7SzrkRUrf7XJ2Na2YsiHjiTGqHIE86KyvxGDhT
# 3n2/jX23Nh/bkWHurHwTfaTCOQ44ZlAbnZQjBlmrFn5hPMXRpciiQFmrKTPD/nuo
# 9MVnCciHEpHJ63/JZNF/eno1122/wVkL7MuRlCVHN7L/wuNQxQk3ARdIju6OD/Gi
# Mwg0Qih6HVWJtkHK3ExoUKKKUZCOvIeHxzp+K6FWUupPZKUgWzn4AHMxm6zr+Sde
# laIAACqAkxYsDYKbM7WlNi3uIH2HeXqU9uSDt5tgPpImrog/ab4HrhpDfITRgT1c
# cxaWQezpJEPC+kqVD41T3wlEie1Qm4vYWg+oBVEMBxVLh6CYbeppCRTEXRGnAiCH
# /Ma1uwyWnNCWxrhd1uSi6sj4ISzgnFyGCvsI0gavKpS5AQhapJgk6/fULTFeS+Ee
# kRH9FDGCAx4wggMaAgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lD
# ZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2ln
# bmluZyBSU0E0MDk2IFNIQTM4NCAyMDIxIENBMQIQDyLHeeRvkUFg5QtSFTT8FjAJ
# BgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0B
# CQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAj
# BgkqhkiG9w0BCQQxFgQU7K/iVMLN1Fry+CQYvvax3elSeT0wDQYJKoZIhvcNAQEB
# BQAEggIAgp12U/BTRoaDLngwZRIR+O/Rq0LkYLWLc3WMVT+NBTlTBGdMtBCVkR4e
# 6hZZkHy+o3+AniCY/vJ5RyEg7ExCMfbsDve8sjD6j66uYeGMg1MGl/puQIiRHdIz
# Q7BTosALnYqGgr46RBmKx694oA8n9vh4uoc0R+SVecax95kkQM1ubcb+VUhXmXeV
# mI+el0BT7yf5S5aub14ACL+sDOtTVaSkFgXqjpx2PhcbxD1fFNI4z0DNTNkzmfD0
# uMjnVxu4rJNd13baODmLvf+kvU3fAEfnRsDch7A056Wk6B19SHeCuxo1sD3tfAof
# xQUQvmtxE4dani6DxjLe7tsCotwRtW/qqj1ZVb0C7eTxV4jjudJFgXCxNgt1Nk73
# VGtLub4UW1rZTCUMayOUoBSKTGqy/wSQZgL/n4YfH0qYBqEHCDqVeiymjUnCUWHe
# Ws/9NnJ3uI8TfduugFiv0SB/8UvuFkwJDEBP4fBGFg7Y/952Hh8Kp+bb3sl6aKRo
# sRgJH/5mXsJBcNYq3bepKTUqy6VrGQ0l1NiaZQ8vJq1fF+rbvY+5T1X0J9hnLG5x
# 1czkIyZtLtVF1vjeIvgv1bGsTpU2Z3DB/Q5mV6IwFlZNsOAnZOxVuTXJcOhMx2FS
# dOveSQAcFfMrvXaGw6AoqebDZ4/TNMLYna+2YMjvem87TU2pIWo=
# SIG # End signature block