Microsoft.Graph.PlusPlus.psm1
using namespace System.Management.Automation using namespace Microsoft.Graph.PowerShell.Models using namespace Microsoft.Graph.PowerShell.Authentication $Global:GraphUri = 'https://graph.microsoft.com/v1.0' #May want this outside the module $Script:GUIDRegex = '^\{?[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}\}?$' $Script:WellKnownMailFolderRegex = '^[/\\]?(archive|clutter|conflicts|conversationhistory|deleteditems|drafts|inbox|junkemail|localfailures|msgfolderroot|outbox|recoverableitemsdeletions|scheduled|searchfolders|sentitems|serverfailures|syncissues)[/\\]?$' $Script:DefaultUserProperties = @('businessPhones', 'displayName', 'givenName', 'id', 'jobTitle', 'mail', 'mobilePhone', 'officeLocation', 'preferredLanguage', 'surname', 'userPrincipalName', 'assignedLicenses', 'department', 'usageLocation', 'userType') $Script:DefaultUsageLocation = 'GB' $Script:SkippedSubmodules = @() #region completer, transformer, and validator attributes for parameters **CLASSES NEED TO BE IN PSM1 class UpperCaseTransformAttribute : ArgumentTransformationAttribute { [object] Transform([System.Management.Automation.EngineIntrinsics]$EngineIntrinsics, [object] $InputData) { if ($inputData -is [string]) {return $Inputdata.toUpper()} else {return ($InputData) } } } class ValidateCountryAttribute : ValidateArgumentsAttribute { [void]Validate([object]$Argument, [EngineIntrinsics]$EngineIntrinsics) { if ($Argument -notin [cultureInfo]::GetCultures("SpecificCultures").foreach({ New-Object -TypeName System.Globalization.RegionInfo -ArgumentList $_.name }).TwoLetterIsoRegionName) { Throw [ParameterBindingException]::new("'$Argument' is not an ISO 3166 country Code") } } } class DomainCompleter : IArgumentCompleter { [array]$Domains = @() [System.Collections.Generic.IEnumerable[CompletionResult]] CompleteArgument( [string]$CommandName, [string]$ParameterName, [string]$WordToComplete, [Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters ) { $result = [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new() if (-not $this.Domains) {$this.Domains = Invoke-GraphRequest "$Global:GraphUri/domains?`$select=id" -ValueOnly | ForEach-Object id | Sort-Object } $wildcard = ('*' + ($wordToComplete -replace '[''"]','' )+ '*') foreach ($d in $this.domains.where({$_ -like $wildcard})) {$result.Add([System.Management.Automation.CompletionResult]::new($_))} return $result } } class GroupCompleter : IArgumentCompleter { [System.Collections.Generic.IEnumerable[CompletionResult]] CompleteArgument( [string]$CommandName, [string]$ParameterName, [string]$WordToComplete, [Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters ) { $result = [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new() #strip quotes from word to complete - replace " or ' with nothing $wordToComplete = $wordToComplete -replace '"|''', '' if ($wordToComplete) {$uri = $Global:GraphUri + ("/Groups/?`$filter=startswith(displayName,'{0}') or startswith(mail,'{0}')" -f $wordToComplete)} else {$uri = "$Global:GraphUri/Groups/?`$Top=20"} Invoke-GraphRequest -Uri $uri -ValueOnly | ForEach-Object displayname | Sort-Object | ForEach-Object { $result.Add([System.Management.Automation.CompletionResult]::new("'$_'", $_, ([CompletionResultType]::ParameterValue) , $_) ) } return $result } } class MailFolderCompleter : IArgumentCompleter { [System.Collections.Generic.IEnumerable[CompletionResult]] CompleteArgument( [string]$CommandName, [string]$ParameterName, [string]$WordToComplete, [Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters ) { $result = [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new() #strip quotes from word to complete - replace " or ' with nothing. $wordToComplete = $wordToComplete -replace '"|''', '' #Where interested in what's before the final / or \ $params = @{'Select' = 'displayname'} $path = '' if ($wordToComplete -match '^[/\\]?(\w.*)[/\\].*?$') { $params['Name'] = $Matches[1]; $params['ChildItems'] =$true $path = $Matches[1] + '/' } if ($FakeBoundParameters['User']) { $params['User'] = $FakeBoundParameters['User']} Get-GraphMailFolder @params | ForEach-Object { $p = $path+$_.displayname if ($p -like "$wordToComplete*") { $result.Add([System.Management.Automation.CompletionResult]::new("'$p'", $p, ([CompletionResultType]::ParameterValue) , $p) ) } } return $result } } class OneDriveFolderCompleter : IArgumentCompleter { [System.Collections.Generic.IEnumerable[CompletionResult]] CompleteArgument( [string]$CommandName, [string]$ParameterName, [string]$WordToComplete, [Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters ) { $result = [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new() #strip quotes from word to complete - replace " or ' with nothing $wordToComplete = $wordToComplete -replace '"|''', '' If ($wordToComplete -notmatch "/.+/" -or $wordToComplete -eq "/root:?/" ) {$params =@{folderPath = '/'} } elseif ($wordToComplete -Match '^/?root:') {$params =@{folderPath = $wordToComplete -replace '^/?(.*)/.*?$', '/$1:'} } #catch after any leading / and before final /; and sandwich between / and : else {$params =@{folderPath = $wordToComplete -replace '^/?(.*)/.*?$','/root:/$1:'} } #catch after any leading / and before final /; and sandwich between /root/ and : if ($FakeBoundParameters['Drive']) { $params['Drive'] = $FakeBoundParameters['Drive']} #I do mean = no -eq in the next line. elseif ($key = $Global:PSDefaultParameterValues.Keys.where({"$CommandName`:Drive" -like $_})) { $params['Drive'] = $Global:PSDefaultParameterValues[$key] } # #it would be better to order-by at the server, but consumer one drive doesn't support it. Get-GraphDrive @params -subFolders -quiet | Sort-Object -Property name | ForEach-Object { $P = ($_.parentReference.path -replace "/drive/|/drives/.*?/","" ) + "/" + $_.name if ($P -like "*$wordToComplete*") { $result.Add([System.Management.Automation.CompletionResult]::new("'$p'", $p, ([CompletionResultType]::ParameterValue) , $p) ) } } return $result } } class OneDrivePathCompleter : IArgumentCompleter { [System.Collections.Generic.IEnumerable[CompletionResult]] CompleteArgument( [string]$CommandName, [string]$ParameterName, [string]$WordToComplete, [Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters ) { $result = [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new() #strip quotes from word to complete - replace " or ' with nothing $wordToComplete = $wordToComplete -replace '"|''', '' If ($wordToComplete -notmatch "/.+/" -or $wordToComplete -eq "/root:?/" ) {$params =@{folderPath = '/'} } elseif ($wordToComplete -Match '^/?root:') {$params =@{folderPath = $wordToComplete -replace '^/?(.*)/.*?$', '/$1:'} } #catch after any leading / and before final /; and sandwich between / and : else {$params =@{folderPath = $wordToComplete -replace '^/?(.*)/.*?$','/root:/$1:'} } #catch after any leading / and before final /; and sandwich between /root/ and : if ($FakeBoundParameters['Drive']) { $params['Drive'] = $FakeBoundParameters['Drive']} #I do mean = no -eq in the next line. elseif ($key = $Global:PSDefaultParameterValues.Keys.where({"$CommandName`:Drive" -like $_})) { $params['Drive'] = $Global:PSDefaultParameterValues[$key] } # #it would be better to order-by at the server, but consumer one drive doesn't support it. Get-GraphDrive -quiet @params | Sort-Object -Property name | ForEach-Object { $P = ($_.parentReference.path -replace "/drive/|/drives/.*?/","" ) + "/" + $_.name if ($P -like "*$wordToComplete*") { $result.Add([System.Management.Automation.CompletionResult]::new("'$p'", $p, ([CompletionResultType]::ParameterValue) , $p) ) } } return $result } } class OneNoteSectionCompleter : IArgumentCompleter { [System.Collections.Generic.IEnumerable[CompletionResult]] CompleteArgument( [string]$CommandName, [string]$ParameterName, [string]$WordToComplete, [Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters ) { $result = [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new() #strip quotes from word to complete - replace " or ' with nothing $wordToComplete = $wordToComplete -replace '"|''', '' $values = @() if ($FakeBoundParameters['Notebook'] -and $FakeBoundParameters['Notebook'].Sections ) {$values=$FakeBoundParameters['Notebook'].Sections.DisplayName} #I do mean = no -eq in the next line. elseif ($key = $Global:PSDefaultParameterValues.Keys.where({"$CommandName`:Notebook" -like $_})) { $values = $Global:PSDefaultParameterValues[$key].Sections.DisplayName } foreach ($p in $values) { if ($P -like "$wordToComplete*" -and $p -match '^\w+$') { $result.Add([System.Management.Automation.CompletionResult]::new($p, $p, ([CompletionResultType]::ParameterValue) , $p) ) } elseif ($P -like "$wordToComplete*") { $result.Add([System.Management.Automation.CompletionResult]::new("'$p'", $p, ([CompletionResultType]::ParameterValue) , $p) ) } } return $result } } class SkuCompleter : IArgumentCompleter { [array]$skus = @() [System.Collections.Generic.IEnumerable[CompletionResult]] CompleteArgument( [string]$CommandName, [string]$ParameterName, [string]$WordToComplete, [Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters ) { $result = [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new() if (-not $this.skus) {$this.skus = Get-GraphSKU } $wildcard = ("*" + ($wordToComplete -replace "['""]",'' )+ "*") $this.skus.where({$_.skuPartNumber -like $wildcard}).skuPartNumber | Sort-Object | ForEach-Object {$result.Add([System.Management.Automation.CompletionResult]::new($_))} return $result } } class SkuPlanCompleter : IArgumentCompleter { [array]$skus = @() [System.Collections.Generic.IEnumerable[CompletionResult]] CompleteArgument( [string]$CommandName, [string]$ParameterName, [string]$WordToComplete, [Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters ) { $result = [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new() if (-not $this.skus) {$this.skus = Get-GraphSKU } $wildcard = ("*" + ($wordToComplete -replace "['""]",'' )+ "*") if ($FakeBoundParameters['SKUID']) { $selectedSkus = $this.skus.where({$_.skuID -in $FakeBoundParameters['SKUID'] -or $_.skuPartNumber -in $FakeBoundParameters['SKUID'] }) } else { $selectedSkus = $this.skus } $selectedSkus.ServicePlans.where({$_.ServicePlanName -like $wildcard}).ServicePlanName | Sort-Object | ForEach-Object {$result.Add([System.Management.Automation.CompletionResult]::new($_))} return $result } } class RoleCompleter : IArgumentCompleter { [System.Collections.Generic.IEnumerable[CompletionResult]] CompleteArgument( [string]$CommandName, [string]$ParameterName, [string]$WordToComplete, [Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters ) { $result = [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new() #strip quotes from word to complete - replace " or ' with nothing if (-not $wordToComplete) {$wordToComplete = '*'} else {$wordToComplete = "$wordToComplete*" -replace '"|''', '' } Invoke-GraphRequest -Uri "$Global:GraphUri/directoryroles?`$select=displayname" -ValueOnly | Where-Object displayname -like $wordToComplete | Sort-Object -Property displayname | ForEach-Object { $result.Add([System.Management.Automation.CompletionResult]::new("'$($_.displayname)'", $_.displayname, ([CompletionResultType]::ParameterValue) , $_.displayname) ) } return $result } } class TeamCompleter : IArgumentCompleter { [System.Collections.Generic.IEnumerable[CompletionResult]] CompleteArgument( [string]$CommandName, [string]$ParameterName, [string]$WordToComplete, [Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters ) { $result = [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new() #strip quotes from word to complete - replace " or ' with nothing $wordToComplete = $wordToComplete -replace '"|''', '' if ($wordToComplete) {$uri = $Global:GraphUri + ("/groups?`$filter=startswith(displayname,'{0}')')" -f $wordToComplete)} else {$uri = "$Global:GraphUri/Groups/?`$Top=20"} #had "ResourceProvisioningOptions eq 'team' and " in the filter but it removed some valid teams so this is just completing groups for now Invoke-GraphRequest -Uri $uri -ValueOnly | ForEach-Object displayname | Sort-Object | ForEach-Object { $result.Add([System.Management.Automation.CompletionResult]::new("'$_'", $_, ([CompletionResultType]::ParameterValue) , $_) ) } return $result } } class UPNCompleter : IArgumentCompleter { [System.Collections.Generic.IEnumerable[CompletionResult]] CompleteArgument( [string]$CommandName, [string]$ParameterName, [string]$WordToComplete, [Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters ) { $result = [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new() #strip quotes from word to complete - replace " or ' with nothing $wordToComplete = $wordToComplete -replace '"|''', '' if ($wordToComplete) { Invoke-GraphRequest -ValueOnly -headers @{'ConsistencyLevel'='eventual'} -uri "$Global:GraphUri/users?`$filter=startswith(userprincipalname,'$wordToComplete')&top=10&select=userprincipalName" | ForEach-Object userPrincipalName | sort-object | ForEach-Object {$result.Add([System.Management.Automation.CompletionResult]::new("'$_'", $_, ([CompletionResultType]::ParameterValue) , $_) )} } return $result } } #endregion . "$PSScriptRoot\Authentication.ps1" #Submodules which need the class and/or private functions from the SDK module. $ImportCmds = [ordered]@{ 'Users' = @('New-MgUserTodoList_CreateExpanded1','New-MgUserTodoListTask_CreateExpanded1', 'Remove-MgUserTodoList_Delete1', 'Remove-MgUserTodoListTask_Delete1', 'Update-MgUserTodoListTask_UpdateExpanded1') #'Get-MgUser_List1' , 'Identity.DirectoryManagement' = @('Get-MgDomain_Get1', 'Get-MgDomain_List1', 'Get-MgDomainNameerenceByRef_List1', 'Get-MgDomainServiceConfigurationRecord_List1' , 'Get-MgDomainVerificationDnsRecord_List1', 'Get-MgOrganization_List1', 'Get-MgSubscribedSku_Get1', 'Get-MgSubscribedSku_List1') 'Users.Functions' = @() 'Users.Actions' = @() 'Identity.SignIns' = @() 'Reports' = @() 'Applications' =('Get-MgServicePrincipal_List1') #Get-MgServicePrincipal_Get2 } foreach ($subModule in $ImportCmds.keys) { $result = $null if (Test-path (Join-Path $PSScriptRoot -ChildPath "Microsoft.Graph.$subModule.private.dll")) { $result = Import-Module -Scope Local (Join-Path $PSScriptRoot -ChildPath "Microsoft.Graph.$subModule.private.dll") -Cmdlet $ImportCmds[$subModule] -PassThru } # I do mean get module and assign it to module and if it works then... not "$module -eq" elseif ($module = Get-Module -ListAvailable "Microsoft.Graph.$submodule" | Sort-Object -Property Version | Select-Object -Last 1) { $result = Import-Module -Scope Local (Join-Path (Split-Path $module.Path) -ChildPath "bin\Microsoft.Graph.$submodule.private.dll") -Cmdlet $ImportCmds[$subModule] -PassThru } else { Write-Verbose "Microsoft.Graph.$subModule.private.dll not found $subModule won't be loaded " $Script:SkippedSubmodules += $subModule } if ($result) { . "$PSScriptRoot\$subModule.ps1" foreach ($cmd in $ImportCmds[$subModule]) { $c = Get-Command $cmd; $c.set_visibility('Private') } } } if ($Script:SkippedSubmodules -contains 'Users') { Write-Verbose "Groups, Notes, OneDrive, Planner, and Sharepoint require the Microsoft.Graph.users module, or Microsoft.Graph.Users.private.dll in the module directory." $Script:SkippedSubmodules += @('Groups', 'Notes', 'OneDrive', 'Planner', 'Sharepoint') } else { #These submodules will work with just the users module. . "$PSScriptRoot\Groups.ps1" . "$PSScriptRoot\Notes.ps1" . "$PSScriptRoot\OneDrive.ps1" . "$PSScriptRoot\Planner.ps1" . "$PSScriptRoot\Sharepoint.ps1" } if ($Script:SkippedSubmodules) { Write-Host -ForegroundColor DarkGray ("Skipped " + ($Script:SkippedSubmodules -join ", ") + " because their Microsoft.Graph module(s) or private.dll file(s) were not found.") } function Set-GraphOptions { <# .synopsis Sets defaults and the tenant client ID & Client Secret used when logging on without a web dialog #> [cmdletbinding()] param ( #Your Tennant ID $TenantID, #Client ID if not using the SDK default of 14d82eec-204b-4c2f-b7e8-296a70dab67e. Must be known to your tennant $ClientID, #Secret set for the client ID in your $TenantID [Alias('Client_Secret,')] $ClientSecret, #Default Scopes to request $DefaultScopes, #Allows a saved Refresh Token (e.g. from Show-GraphSession) to be added to the session. $RefreshToken, #Changes the dafault properties returned by Get-GraphUser and Get-GraphUserList [validateSet('accountEnabled', 'ageGroup', 'assignedLicenses', 'assignedPlans', 'businessPhones', 'city', 'companyName', 'consentProvidedForMinor', 'country', 'createdDateTime', 'department', 'displayName', 'givenName', 'id', 'imAddresses', 'jobTitle', 'legalAgeGroupClassification', 'mail', 'mailNickname', 'mobilePhone', 'officeLocation', 'onPremisesDomainName', 'onPremisesExtensionAttributes', 'onPremisesImmutableId', 'onPremisesLastSyncDateTime', 'onPremisesProvisioningErrors', 'onPremisesSamAccountName', 'onPremisesSecurityIdentifier', 'onPremisesSyncEnabled', 'onPremisesUserPrincipalName', 'passwordPolicies', 'passwordProfile', 'postalCode', 'preferredDataLocation', 'preferredLanguage', 'provisionedPlans', 'proxyAddresses', 'state', 'streetAddress', 'surname', 'usageLocation', 'userPrincipalName', 'userType')] [string[]]$DefaultUserProperties, #Changes the default two letter (ISO 3166) country code - for new users so they can be assigned licenses. Examples include: 'US', 'JP', and 'GB' [ValidateNotNullOrEmpty()] [ValidateCountryAttribute()] [string]$DefaultUsageLocation ) if ($TenantID) { if ($TenantID -notmatch $GUIDRegex) { Write-Warning 'TenantID should be a GUID' ; break } else {$Script:TenantID = $TenantID} } if ($ClientID) { if ($Clientid -notmatch $GUIDRegex) { Write-Warning 'ClientID should be a GUID' ; break } else {$Script:ClientID = $ClientID} } if ($ClientSecret) { if ($ClientSecret -is [string]) { $Script:ClientSecret = $ClientSecret } elseif ($ClientSecret -is [securestring]) { $Script:ClientSecret = (new-object pscredential -ArgumentList "NoName", $ClientSecret).GetNetworkCredential().Password } else {Write-Warning 'ClientSecret should be a string or preferably a securestring' ; break} } if ($Script:TenantID) {Write-Verbose "TenantID: '$Script:TenantID' , ClientID: '$Script:ClientID'"} if ($DefaultScopes) {$Script:DefaultGraphScopes = $DefaultScopes} Write-Verbose ('Scopes: ' + ($Script:DefaultGraphScopes -join ', ')) if ($RefreshToken) {$Script:RefreshToken = $RefreshToken } if ($DefaultUserProperties) {$Script:DefaultUserProperties = $DefaultUserProperties} if ($DefaultUsageLocation) {$Script:DefaultUsageLocation = $DefaultUsageLocation.ToUpper()} } #call a script with calls to Set-GraphOptions if ($env:GraphSettingsPath ) {. $env:GraphSettingsPath} elseif (Test-Path "$PSScriptRoot\Microsoft.Graph.PlusPlus.settings.ps1") {. "$PSScriptRoot\Microsoft.Graph.PlusPlus.settings.ps1"} if ($null -eq [GraphSession]::instance.AuthContext) {Write-Host "Ready for Connect-Graph."} elseif ([GraphSession]::instance.AuthContext.AppName -and -not [GraphSession]::instance.AuthContext.Account) { Write-Host ("Already logged on as the app '$([GraphSession]::instance.AuthContext.AppName)'." )} else {Write-Host ("Already logged on as '$([GraphSession]::instance.AuthContext.Account)'." )} |