public/Enable-UcmO365Service.ps1
#PerformScriptSigning Function Enable-UcmO365Service { <# .SYNOPSIS Enables a Specified Service Plan in Office365 .DESCRIPTION Cmdlet uses an existing Azure AD connection to filter through a users licences and enable the requested service. Handy if an administrator has disabled Skype for Business Online for example. .EXAMPLE PS> Enable-UcmO365Service -User 'button.mash@Contoso.com' -ServiceName 'MCOSTANDARD' Enables Skype for Business Online for the user Button Mash PS> Enable-UcmO365Service -UPN 'button.mash@contoso.com' -ServiceName 'TEAMS1' Enables Microsoft Teams for the user Button Mash PS> Enable-UcmO365Service -UPN 'button.mash@contoso.com' -ServiceName 'MCOPSTNEAU' Enables Telstra Calling (Australian version of Microsoft Calling) for the user Button Mash .PARAMETER UPN The users username in UPN format .PARAMETER ServiceName Office365 Service Plan you wish to enable .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 three values "OK" : The Service Plan was enabled "Error" : The Service Plan was wasnt enabled, it may not have been found or there was an error setting the users attributes. "Unknown" : Cmdlet reached the end of the function 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.3 Date: 13/07/2021 .VERSION HISTORY 1.3: Added a check and return if the service is already enabled instead of getting caught in the catchall error Updated Catchall error message Added MSOL cmdlet error catching and return Added a check to see if the Service Plan existss for that user Added a check to see if the user is licenced at all 1.2: Fixed issue with random "-Message" messages being written to the pipeline Added check to only attempt to write the service changes if we actually changed something Added more infomative error messages when the cmdet fails to set a service 1.1: Updated to "Ucm" naming convention Better inline documentation 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 Write-HTMLReport: https://github.com/Atreidae/UcmPsTools/blob/main/public/Write-HTMLReport.ps1 (optional) .REQUIRED PERMISSIONS 'Office365 User Admin' or better .LINK https://www.UcMadScientist.com https://github.com/Atreidae/UcmPSTools .ACKNOWLEDGEMENTS Stack Overflow, disabling services: https://stackoverflow.com/questions/50492591/how-can-i-disable-and-enable-office-365-apps-for-all-users-at-once-using-powersh Alex Verboon, Powershell script to remove Office 365 Service Plans from a User: https://www.verboon.info/2015/12/powershell-script-to-remove-office-365-service-plans-from-a-user/ #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '', Scope='Function')] #todo, https://github.com/Atreidae/UcmPSTools/issues/23 Param ( [Parameter(ValueFromPipelineByPropertyName=$true, Mandatory, Position=1,HelpMessage='The UPN of the user you wish to enable the Service Plan on, eg: button.mash@contoso.com')] [string]$UPN, [Parameter(ValueFromPipelineByPropertyName=$true, Mandatory, Position=2,HelpMessage="The name of the Office365 Service Plan you wish enable, eg: 'MCOSTANDARD' for Skype Online")] [string]$ServiceName ) #region FunctionSetup, Set Default Variables for HTML Reporting and Write Log $function = 'Enable-UcmO365Service' [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 #Set a flag to track if we enabled the app or not $AppEnabled = $False #Set a flag to track if we found the app or not $servicePlanExists = $False #Check the user is even licenced in O365 If ((Get-MsolUser -UserPrincipalName $UPN).isLicensed -ne $true) { Write-UcmLog -Message 'User does not have ANY valid O365 licence, abort' -Severity 3 -Component $function $Return.Status = 'Error' $Return.Message = "User has no O365 licence, run 'Get-MsolUser -UserPrincipalName $Upn' for more info" Return $Return } #Get the user Licence details $LicenseDetails = (Get-MsolUser -UserPrincipalName $UPN).Licenses #Run through all the servicesplans on each licence, one licence at a time. ForEach ($License in $LicenseDetails) { Write-UcmLog -Message "Checking $($License.AccountSkuId) for $Servicename" -Severity 1 -Component $function #Find all the Disabled services and add them to an array, except for the requsted service. We need them for later $DisabledOptions = @() ForEach ($Service in $License.ServiceStatus) { #Check this service plan Write-UcmLog -Message "Checking $($Service.ServicePlan.ServiceName)" -Severity 1 -Component $function #check if we this is the plan and its already enabled If ($Service.ServicePlan.ServiceName -eq $ServiceName) { Write-UcmLog -Message "Found requested Serviceplan" -Severity 1 -Component $function $servicePlanExists = $True If ($Service.ProvisioningStatus -eq 'Success') { Write-UcmLog -Message "Service Plan is already enabled, returning" -Severity 1 -Component $function $Return.Status = 'OK' $Return.Message = 'Already Enabled' Return $Return } } #Else check if the ServicePlan is disabled so we can track it (and enable it if needed) If ($Service.ProvisioningStatus -eq 'Disabled') { #The Service Is disabled, check to see if its the requested service Write-UcmLog -Message "Service Plan is currently Disabled" -Severity 1 -Component $function If ($Service.ServicePlan.ServiceName -eq $ServiceName) { Write-UcmLog -Message "$Servicename Was disabled, Enabling" -Severity 2 -Component $function $AppEnabled = $true } #Not the requested service, add it to the array Else { Write-UcmLog -Message "$($Service.ServicePlan.ServiceName) is disabled, adding to Disabled Options" -Severity 1 -Component $function $DisabledOptions += "$($Service.ServicePlan.ServiceName)" } } Else #$Service.ProvisioningStatus check { Write-UcmLog -Message "Service Plan is currently Enabled" -Severity 1 -Component $function } } #Did we change any services? if so. Set the licence options using the new list of disabled licences If ($AppEnabled -eq $true) { Try { Write-UcmLog -Message 'Setting Licence Options with the following Disabled Services' -Severity 1 -Component $function Write-UcmLog -Message "$DisabledOptions" -Severity 1 -Component $function #If there are zero options in the disabled list, dont use the -DisabledPlans flag. If ($DisabledOptions.count -eq 0) { $LicenseOptions = New-MsolLicenseOptions -AccountSkuId $License.AccountSkuId } Else { $LicenseOptions = New-MsolLicenseOptions -AccountSkuId $License.AccountSkuId -DisabledPlans $DisabledOptions } #Using the License Options attributes, set the users licence. Set-MsolUserLicense -UserPrincipalName $UPN -LicenseOptions $LicenseOptions #Reset the AppEnabled Flag $AppEnabled = $False #Set a flag so we can see it was changed $MadeChanges= $True } Catch #Something went wrong setting user licence { Write-UcmLog -Message 'Something went wrong assinging the licence' -Severity 3 -Component $function $AppEnabled = $false $MadeChanges= $False $Return.Status = 'Error' $Return.Message = 'Error running New-MsolLicenseOptions or Set-MsolUserLicense' Return $Return } } #Otherwise we didnt change anything, no need to rewrite the licence } #Repeat for the next Licence #Does the service plan even exist? Abort if not If ($servicePlanExists -ne $true) { Write-UcmLog -Message "Could not locate $Servicename on user $UPN, make sure the appropriate licence is assigned" -Severity 3 -Component $function $Return.Status = 'Error' $Return.Message = "Unable to locate $ServiceName" Return $Return } #Report on success/failure based on the $AppEnabled flag If ($MadeChanges){ $Return.Status = 'OK' $Return.Message = 'Enabled' Return $Return } Else{ Write-UcmLog -Message "No Services were enabled, Either the service is provisioning, has an error, or it is not available with the current assigned licences" -Severity 3 -Component $function $Return.Status = 'Error' $Return.Message = 'Service exists, but returned unknown state' Return $Return } #endregion FunctionWork #region FunctionReturn #Default Return Variable for UcmPsTools HTML Reporting Fucntion Write-UcmLog -Message "Reached end of $function without a Return Statement" -Severity 3 -Component $function $return.Status = 'Unknown' $return.Message = 'Function did not encounter return statement' Return $Return #endregion FunctionReturn } # SIG # Begin signature block # MIIRwgYJKoZIhvcNAQcCoIIRszCCEa8CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUigki/ZZjqG/aPAdRRYRYvpYX # OpSggg4OMIIGsDCCBJigAwIBAgIQCK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0B # 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 # BgkqhkiG9w0BCQQxFgQUDhDAPMaem1wQqcAXYzCeAh3ENkcwDQYJKoZIhvcNAQEB # BQAEggIAFfnq0f8T0GV7bz1TO1LTSQtuANTg2On+xyiH6xnQ+68PmzEIP8EjFsBe # k0Uibeph6K4VFdScE7hJhl0sibaPtmYT4Hw4SCD32VBUplOlIUQoTH/wmZgm12aW # u9A2nw5fyGklXN9HrW6a7gn8JAHjdbxm1QjBxGdDpa2v4IviIAG9VQq7ZAVT5+ZS # U84cEGQcgbD4lKaFMWFfPK4vfUJzPYU2cBUnvlEXOSpDEFvRxr3RJGVNB8bnlQK8 # wZlDn5X28xC93MfEwinFqjDYH+gMwQOHPY24jAGNC9K8WpZ15pO+4Z21ovQ023Wz # 6O+HFdHFKf8qyP1YHxsEY/otOfBKf+xkMJ5ZUO5ThdKSo9oPRzWlP6dqnwojyG4U # I6URPmR2Omx102zizKsNrQww0kG0KkRT4+HN0eHL1CV+KPG761Tkqg1fV9T42lm+ # iBuV5Y5zEPP/rh+diwjRkGMBJ0Ar20VwM7Yg61Tq2vOe+f/eA0CVN6iMoTkR16PM # G+vUqVp9S2i2uuglZywedSH0MQ9fSYchVYckbQYwcgJcqTJUNlPD7zB3VpHYu2R4 # yfCtn8cUJiJSWzvrPklRew36LwZKy3OjEznvrOrMdCeLjXBOavuF+FVwJe9jCeEz # c0+RnnTLD6f4P2aPDAtZco7Xh7bXv7QT7Y2crAPEH0ZM6p2Qi3o= # SIG # End signature block |