-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathUsers.ps1
2767 lines (2548 loc) · 142 KB
/
Users.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
using namespace Microsoft.Graph.PowerShell.Models
function ConvertTo-GraphUser {
<#
.Synopsis
Helper function (not exported) to expand users' manager or direct reports when converting results to userObjects
#>
param (
#The dictionary /hash table object returned by the REST API
[Parameter(ValueFromPipeline=$true,Mandatory=$true,Position=0)]
$RawUser
)
process {
foreach ($r in $RawUser) {
#We expand manager by default, or might be told to expand direct reports. Make either into users.
if (-not $r.manager) { $mgr = $null}
else {
$disallowedProperties = $r.manager.keys.where({$_ -notin $script:UserProperties})
foreach ($p in $disallowedProperties) {$null = $r.manager.remove($p)}
$mgr = New-Object -TypeName MicrosoftGraphUser -Property $r.manager
$null = $r.remove('manager')
}
if (-not $r.directReports) {$directs = $null}
else {
$directs = @()
foreach ($d in $r.directReports) {
$disallowedProperties = $d.keys.where({$_ -notin $script:UserProperties})
foreach ($p in $disallowedProperties) {$null = $d.remove($p)}
$directs += New-Object -TypeName MicrosoftGraphUser -Property $d
}
$null = $r.remove('directReports')
}
$disallowedProperties = $r.keys.where({$_ -notin $script:UserProperties})
foreach ($p in $disallowedProperties) {$null = $r.remove($p)}
$user = New-Object -TypeName MicrosoftGraphUser -Property $r
if ($mgr) {$user.manager = $mgr}
if ($directs) {$user.DirectReports= $directs}
$user
}
}
}
function Get-GraphUserList {
<#
.Synopsis
Returns a list of Azure active directory users for the current tennant.
.Example
Get-GraphUserList -filter "Department eq 'Accounts'"
Gets the list with a custom filter this is typically fieldname eq 'value' for equals or
startswith(fieldname,'value') clauses can be joined with and / or.
#>
[OutputType([Microsoft.Graph.PowerShell.Models.MicrosoftGraphUser])]
[cmdletbinding(DefaultparameterSetName="None")]
param (
#If specified searches for users whose first name, surname, displayname, mail address or UPN start with that name.
[parameter(Mandatory=$true, parameterSetName='FilterByName', Position=0,ValueFromPipeline=$true )]
[ArgumentCompleter([UPNCompleter])]
[string[]]$Name,
#Names of the fields to return for each user.Note that some properties - aboutMe, Birthday etc, are only available when getting a single user, not a list.
#The API defaults to : businessPhones, displayName, givenName, id, jobTitle, mail, mobilePhone, officeLocation, preferredLanguage, surname, userPrincipalName
#The module adds to this set - the default list can be set with Set-GraphOption -DefaultUserProperties which has the master copy of the validate set used here
[validateSet('accountEnabled', 'ageGroup', 'assignedLicenses', 'assignedPlans', 'businessPhones',
'city', 'companyName', 'consentProvidedForMinor', 'country', 'createdDateTime', 'creationType',
'deletedDateTime', 'department', 'displayName',
'employeeHireDate', 'employeeID', 'employeeOrgData', 'employeeType', 'externalUserState', 'externalUserStateChangeDateTime',
'givenName', 'id', 'identities', 'imAddresses', 'isResourceAccount','jobTitle', 'legalAgeGroupClassification',
'mail', 'mailNickname', 'mobilePhone',
'officeLocation', 'onPremisesDistinguishedName', 'onPremisesDomainName', 'onPremisesExtensionAttributes',
'onPremisesImmutableId', 'onPremisesLastSyncDateTime', 'onPremisesProvisioningErrors', 'onPremisesSamAccountName', 'otherMails',
'onPremisesSecurityIdentifier', 'onPremisesSyncEnabled', 'onPremisesUserPrincipalName',
'passwordPolicies', 'passwordProfile', 'postalCode', 'preferredDataLocation',
'preferredLanguage', 'provisionedPlans', 'proxyAddresses',
'showInAddressList','state', 'streetAddress', 'surname', 'usageLocation', 'userPrincipalName', 'userType')]
[Alias('Property')]
[string[]]$Select = $Script:DefaultUserProperties ,
#The default is to get all
$Top ,
#Order by clause for the query - most fields result in an error and it can't be combined with some other query values.
[parameter(Mandatory=$true, parameterSetName='Sorted')]
[ValidateSet('displayName', 'userPrincipalName')]
[Alias('OrderBy')]
[string]$Sort,
#Filter clause for the query for example "startswith(displayname,'Bob') or startswith(displayname,'Robert')"
[parameter(Mandatory=$true, parameterSetName='FilterByString')]
[string]$Filter,
#Adds a filter clause "userType eq 'Member'"
[parameter(Mandatory=$true, parameterSetName='FilterToMembers')]
[switch]$MembersOnly,
#Adds a filter clause "userType eq 'Guest'"
[parameter(Mandatory=$true, parameterSetName='FilterToGuests')]
[switch]$GuestsOnly,
[validateSet('directReports', 'manager', 'memberOf', 'ownedDevices', 'ownedObjects', 'registeredDevices', 'transitiveMemberOf', 'extensions','')]
[string]$ExpandProperty = 'manager',
# The URI for the proxy server to use
[Parameter(DontShow)]
[System.Uri]
$Proxy,
# Credentials for a proxy server to use for the remote call
[Parameter(DontShow)]
[ValidateNotNull()]
[PSCredential]$ProxyCredential,
# Use the default credentials for the proxygit
[Parameter(DontShow)]
[Switch]$ProxyUseDefaultCredentials
)
process {
Write-Progress "Getting the list of Users"
$webParams = @{ValueOnly = $true }
if ($MembersOnly) {$Filter = "userType eq 'Member'"}
elseif ($GuestsOnly) {$Filter = "userType eq 'Guest'"}
if ($Filter -or $Sort -or $Name) {
$webParams['Headers'] = @{'ConsistencyLevel'='eventual'}
}
#Ensure at least ID, UPN and displayname are selected - and we always have something in $select
foreach ($s in @('ID', 'userPrincipalName', 'displayName')){
if ($s -notin $Select) {$Select += $s }
}
$uri = "$GraphUri/users?`$select=" + ($Select -join ',')
if (-not $Top) {$webParams['AllValues'] = $true }
else {$uri = $uri + '&$top=' + $Top }
if ($Filter) {$uri = $uri + '&$Filter=' + $Filter }
if ($Sort) {$uri = $uri + '&$orderby=' + $Sort }
if ($ExpandProperty) {$uri = $uri + '&expand=' + $ExpandProperty }
if (-not $Name) {Invoke-GraphRequest -Uri $uri @webParams | ConvertTo-GraphUser}
else {
foreach ($n in $Name) {
$filter = '&$Filter=' + (FilterString $n -ExtraFields 'userPrincipalName','givenName','surname','mail')
Invoke-GraphRequest -Uri ($uri + $filter) @webParams | ConvertTo-GraphUser
}
}
Write-Progress "Getting the list of Users" -Completed
}
}
function Get-GraphUser {
<#
.Synopsis
Gets information from the MS-Graph API about the a user (current user by default)
.Description
Queries https://graph.microsoft.com/v1.0/me or https://graph.microsoft.com/v1.0/name@domain
or https://graph.microsoft.com/v1.0/<<guid>> for information about a user.
Getting a user returns a default set of properties only (businessPhones, displayName, givenName,
id, jobTitle, mail, mobilePhone, officeLocation, preferredLanguage, surname, userPrincipalName).
Use -select to get the other properties.
Most options need consent to use the Directory.Read.All or Directory.AccessAsUser.All scopes.
Some options will also work with user.read; and the following need consent which is task specific
Calendars needs Calendars.Read, OutLookCategries needs MailboxSettings.Read, PlannerTasks needs
Group.Read.All, Drive needs Files.Read (or better), Notebooks needs either Notes.Create or
Notes.Read (or better).
.Example
Get-GraphUser -MemberOf | ft displayname, description, mail, id
Shows the name description, email address and internal ID for the groups this user is a direct member of
.Example
(get-graphuser -Drive).root.children.name
Gets the user's one drive. The drive object has a .root property which is represents its
root-directory, and this has a .children property which is a collection of the objects
in the root directory. So this command shows the names of files and folders in the root directory. To just see sub folders it is possible to use
get-graphuser -Drive | Get-GraphDrive -subfolders
#>
[cmdletbinding(DefaultparameterSetName="None")]
[Alias('ggu')]
[OutputType([Microsoft.Graph.PowerShell.Models.MicrosoftGraphUser])]
param (
#UserID as a guid or User Principal name. If not specified, it will assume "Current user" if other paraneters are given, or "All users" otherwise.
[parameter(Position=0,valueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[alias('id')]
[ArgumentCompleter([UPNCompleter])]
$UserID,
#Get the user's Calendar(s)
[parameter(Mandatory=$true, parameterSetName="Calendars")]
[switch]$Calendars,
#Select people who have the user as their manager
[parameter(Mandatory=$true, parameterSetName="DirectReports")]
[switch]$DirectReports,
#Get the user's one drive
[parameter(Mandatory=$true, parameterSetName="Drive")]
[switch]$Drive,
#Get user's license Details
[parameter(Mandatory=$true, parameterSetName="LicenseDetails")]
[switch]$LicenseDetails,
#Get the user's Mailbox Settings
[parameter(Mandatory=$true, parameterSetName="MailboxSettings")]
[switch]$MailboxSettings,
#Get the users Outlook-categories (by default, 6 color names)
[parameter(Mandatory=$true, parameterSetName="OutlookCategories")]
[switch]$OutlookCategories,
#Get the user's manager
[parameter(Mandatory=$true, parameterSetName="Manager")]
[switch]$Manager,
#Get the user's teams
[parameter(Mandatory=$true, parameterSetName="Teams")]
[switch]$Teams,
#Get the user's Groups
[parameter(Mandatory=$true, parameterSetName="Groups")]
[switch]$Groups,
[parameter(Mandatory=$false, parameterSetName="Groups")]
[parameter(Mandatory=$true, parameterSetName="SecurityGroups")]
[switch]$SecurityGroups,
#Get the Directory-Roles and Groups the user belongs to; -Groups or -Teams only return one type of object.
[parameter(Mandatory=$true, parameterSetName="MemberOf")]
[switch]$MemberOf,
#Get the Directory-Roles and Groups the user belongs to; -Groups or -Teams only return one type of object.
[parameter(Mandatory=$true, parameterSetName="TransitiveMemberOf")]
[switch]$TransitiveMemberOf,
#Get the user's Notebook(s)
[parameter(Mandatory=$true, parameterSetName="Notebooks")]
[switch]$Notebooks,
#Get the user's photo
[parameter(Mandatory=$true, parameterSetName="Photo")]
[switch]$Photo,
#Get the user's assigned tasks in planner.
[parameter(Mandatory=$true, parameterSetName="PlannerTasks")]
[Alias('AssignedTasks')]
[switch]$PlannerTasks,
#Get the plans owned by the user in planner.
[parameter(Mandatory=$true, parameterSetName="PlannerPlans")]
[switch]$Plans,
#Get the users presence in Teams
[parameter(Mandatory=$true, parameterSetName="Presence")]
[switch]
$Presence,
#Get the user's MySite in SharePoint
[parameter(Mandatory=$true, parameterSetName="Site")]
[switch]$Site,
#Get the user's To-do lists
[parameter(Mandatory=$true, parameterSetName="ToDoLists")]
[switch]$ToDoLists,
#specifies which properties of the user object should be returned Additional options are available when selecting individual users
#The API documents list deviceEnrollmentLimit, deviceManagementTroubleshootingEvents , mailboxSettings which cause errors
[parameter(Mandatory=$true,parameterSetName="Select")]
[ValidateSet ('aboutMe', 'accountEnabled' , 'activities', 'ageGroup', 'agreementAcceptances' , 'appRoleAssignments',
'assignedLicenses', 'assignedPlans', 'authentication', 'birthday', 'businessPhones',
'calendar', 'calendarGroups', 'calendars', 'calendarView', 'city', 'companyName', 'consentProvidedForMinor',
'contactFolders', 'contacts', 'country', 'createdDateTime', 'createdObjects', 'creationType' ,
'deletedDateTime', 'department', 'directReports', 'displayName', 'drive', 'drives',
'employeeHireDate', 'employeeId', 'employeeOrgData', 'employeeType', 'events', 'extensions',
'externalUserState', 'externalUserStateChangeDateTime', 'faxNumber', 'followedSites', 'givenName', 'hireDate',
'id', 'identities', 'imAddresses', 'inferenceClassification', 'insights', 'interests', 'isResourceAccount', 'jobTitle', 'joinedTeams',
'lastPasswordChangeDateTime', 'legalAgeGroupClassification', 'licenseAssignmentStates', 'licenseDetails' ,
'mail' , 'mailFolders' , 'mailNickname', 'managedAppRegistrations', 'managedDevices', 'manager' , 'memberOf' , 'messages', 'mobilePhone', 'mySite',
'oauth2PermissionGrants' , 'officeLocation', 'onenote', 'onlineMeetings' , 'onPremisesDistinguishedName', 'onPremisesDomainName',
'onPremisesExtensionAttributes', 'onPremisesImmutableId', 'onPremisesLastSyncDateTime', 'onPremisesProvisioningErrors', 'onPremisesSamAccountName',
'onPremisesSecurityIdentifier', 'onPremisesSyncEnabled', 'onPremisesUserPrincipalName', 'otherMails', 'outlook', 'ownedDevices', 'ownedObjects',
'passwordPolicies', 'passwordProfile', 'pastProjects', 'people', 'photo', 'photos', 'planner', 'postalCode', 'preferredLanguage',
'preferredName', 'presence', 'provisionedPlans', 'proxyAddresses', 'registeredDevices', 'responsibilities',
'schools', 'scopedRoleMemberOf', 'settings', 'showInAddressList', 'signInSessionsValidFromDateTime', 'skills', 'state', 'streetAddress', 'surname',
'teamwork', 'todo', 'transitiveMemberOf', 'usageLocation', 'userPrincipalName', 'userType')]
[String[]]$Select = $Script:DefaultUserProperties ,
#Used to explicitly say "Current user" and will over-ride UserID if one is given.
[switch]$Current
)
process {
$result = @()
if ((ContextHas -Not -WorkOrSchoolAccount) -and ($MailboxSettings -or $Manager -or $Photo -or $DirectReports -or $LicenseDetails -or $MemberOf -or $Teams -or $PlannerTasks -or $Devices )) {
Write-Warning -Message "Only the -Drive, -Calendars and -Notebooks options work when you are logged in with this kind of account." ; return
#to do check scopes.
# Most options need consent to use the Directory.Read.All or Directory.AccessAsUser.All scopes.
# Some options will also work with user.read; and the following need consent which is task specific
# Calendars needs Calendars.Read, OutLookCategries needs MailboxSettings.Read, PlannerTasks needs
# Group.Read.All, Drive needs Files.Read (or better), Notebooks needs either Notes.Create or Notes.Read (or better).
}
#region resolve User name(s) to IDs,
#If we got -Current use the "me" path - otherwise if we didn't get an ID return the list. So Get-Graphuser = list; Get-Graphuser -current = me ; Get-graphuser -memberof = memberships for me; otherwise we get a name or ID
if ($Current -or ($PSBoundParameters.Keys.Where({$_ -notin [cmdlet]::CommonParameters}) -and -not $UserID) ) {$UserID = "me"}
elseif (-not $UserID) {
Get-GraphUserList ;
return
}
#if we got a user object use its ID, if we got an array and it contains names (not guid or UPN or "me") and also contains Guids we can't unravel that.
if ($UserID -is [array] -and $UserID -notmatch "$GuidRegex|\w@\w|^me`$" -and
$UserID -match $GuidRegex ) {
Write-Warning -Message 'If you pass an array of values they cannot be names. You can pipe names or pass and array of IDs/UPNs' ; return
}
#if it is a string and not a guid or UPN - or an array where at least some members are not GUIDs/UPN/me try to resolve it
elseif (($UserID -is [string] -or $UserID -is [array]) -and
$UserID -notmatch "$GuidRegex|\w@\w|^me`$" ) {
$UserID = Get-GraphUserList -Name $UserID
}
#endregion
[void]$PSBoundParameters.Remove('UserID')
foreach ($u in $UserID) {
#region set up the user part of the URI that we will call
if ($u -is [MicrosoftGraphUser] -and -not ($PSBoundParameters.Keys.Where({$_ -notin [cmdlet]::CommonParameters}) )) {
$u
continue
}
if ($u.id) { $id = $u.Id}
else { $id = $u }
if ($id -notmatch "^me$|$guidRegex|\w@\w") {
Write-Warning "User ID '$id' does not look right"
}
Write-Progress -Activity 'Getting user information' -CurrentOperation "User = $id"
if ($id -eq 'me') { $Uri = "$GraphUri/me" }
else { $Uri = "$GraphUri/users/$id" }
# -Teams requires a GUID, photo doesn't work for "me"
if ( (($Teams -or $Presence) -and $id -notmatch $GuidRegex ) -or
($Photo -and $id -eq 'me') ) {
$id = (Invoke-GraphRequest -Method GET -Uri $uri).id
$Uri = "$GraphUri/users/$id"
}
#endregion
#region add the data-specific part of the URI, make the rest call and convert the result to the desired objects
<#available: but not implemented in this command (some in other commands )
managedAppRegistrations, appRoleAssignments,
activities & activities/recent, needs UserActivity.ReadWrite.CreatedByApp permission
calendarGroups, calendarView, contactFolders, contacts, mailFolders, messages,
createdObjects, ownedObjects,
managedDevices, registeredDevices, deviceManagementTroubleshootingEvents,
events, extensions,
followedSites,
inferenceClassification,
insights/used" /trending or /stored.
oauth2PermissionGrants,
onlineMeetings,
photos,
presence,
scopedRoleMemberOf,
(content discovery) settings,
teamwork (apps),
"https://graph.microsoft.com/v1.0/me/getmemberobjects" -body '{"securityEnabledOnly": false}' ).value
#>
try {
if ($Drive -and (ContextHas -WorkOrSchoolAccount)) {
Invoke-GraphRequest -Uri ($uri + '/Drive?$expand=root($expand=children)') -PropertyNotMatch '@odata' -As ([MicrosoftGraphDrive]) }
elseif ($Drive ) {
Invoke-GraphRequest -Uri ($uri + '/Drive') -As ([MicrosoftGraphDrive]) }
elseif ($LicenseDetails ) {
Invoke-GraphRequest -Uri ($uri + '/licenseDetails') -All -As ([MicrosoftGraphLicenseDetails]) }
elseif ($MailboxSettings ) {
Invoke-GraphRequest -Uri ($uri + '/MailboxSettings') -Exclude '@odata.context' -As ([MicrosoftGraphMailboxSettings])}
elseif ($OutlookCategories ) {
Invoke-GraphRequest -Uri ($uri + '/Outlook/MasterCategories') -All -As ([MicrosoftGraphOutlookCategory]) }
elseif ($Photo ) {
$response = Invoke-GraphRequest -Uri ($uri + '/Photo')
if ($response.'@odata.context' -match "#users\('(.*)'\)/") {
$picUserId = $Matches[1]
}
else {$picUserId = $null}
if ($response.'@odata.mediaContentType' -and
$PSVersionTable.Platform -like "win*" -and
(Test-Path "HKLM:\SOFTWARE\Classes\MIME\Database\Content Type\$($response.'@odata.mediaContentType')")) {
$picExtension = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Classes\MIME\Database\Content Type\$($response.'@odata.mediaContentType')").Extension
}
else {$picExtension = $null}
$Null = $response.Remove('@odata.mediaEtag'), $response.Remove('@odata.context'), $response.Remove('@odata.id'), $response.Remove('@odata.mediaContentType')
New-Object -TypeName MicrosoftGraphProfilePhoto -Property $response |
Add-Member -PassThru -NotePropertyName UserID -NotePropertyValue $picUserId |
Add-Member -PassThru -NotePropertyName Ext -NotePropertyValue $picExtension |
Add-Member -PassThru -NotePropertyName URI -NotePropertyValue ($uri + '/Photo/$value') |
Add-Member -PassThru -MemberType ScriptMethod -Name Download -value{
param ($Path="$Pwd\userPhoto", [switch]$Passthru)
if ($path -notlike "*$($this.ext)") {$Path += $this.ext}
if ($this.uri) {Invoke-GraphRequest $this.uri -OutputFilePath $path}
if ($Passthru) {Get-Item $path}
}
}
elseif ($PlannerTasks ) {
Invoke-GraphRequest -Uri ($uri + '/planner/tasks') -All -Exclude '@odata.etag' -As ([MicrosoftGraphPlannerTask])}
elseif ($Plans ) {
Invoke-GraphRequest -Uri ($uri + '/planner/plans') -All -Exclude "@odata.etag" -As ([MicrosoftGraphPlannerPlan])}
elseif ($Presence ) {
if ($u.DisplayName) {$displayName = $u.DisplayName} else {$displayName=$null}
if ($u.UserPrincipalName) {$upn = $u.UserPrincipalName}
elseif ($u -match '\w@\w') {$upn = $u}
else {$upn = $null}
#can also use GET https://graph.microsoft.com/v1.0/communications/presences/<<id>>>
#see https://docs.microsoft.com/en-us/graph/api/cloudcommunications-getpresencesbyuserid for getting bulk presence
Invoke-GraphRequest -Uri ($uri + '/presence') -Exclude "@odata.context" -As ([MicrosoftGraphPresence]) |
Add-Member -PassThru -NotePropertyName DisplayName -NotePropertyValue $displayName |
Add-Member -PassThru -NotePropertyName UserPrincipalName -NotePropertyValue $upn
}
elseif ($Teams ) {
Invoke-GraphRequest -Uri ($uri + '/joinedTeams') -All -As ([MicrosoftGraphTeam])}
elseif ($ToDoLists ) {
Invoke-GraphRequest -Uri ($uri + '/todo/lists') -All -Exclude "@odata.etag" -As ([MicrosoftGraphTodoTaskList]) |
Add-Member -PassThru -NotePropertyName UserId -NotePropertyValue $id
}
# Calendar wants a property added so we can find it again
elseif ($Calendars ) {
Invoke-GraphRequest -Uri ($uri + '/Calendars?$orderby=Name' ) -All -As ([MicrosoftGraphCalendar]) |
ForEach-Object {
if ($id -eq 'me') {$calpath = "me/Calendars/$($_.id)"}
else {$calpath = "users/$id/calendars/$($_.id)"
Add-Member -InputObject $_ -NotePropertyName User -NotePropertyValue $id
}
Add-Member -PassThru -InputObject $_ -NotePropertyName CalendarPath -NotePropertyValue $calpath
}
}
elseif ($Notebooks ) {
$response = Invoke-GraphRequest -Uri ($uri +
'/onenote/notebooks?$expand=sections' ) -All -Exclude '[email protected]' -As ([MicrosoftGraphNotebook])
#Section fetched this way won't have parentNotebook, so make sure it is available when needed
foreach ($bookobj in $response) {
foreach ($s in $bookobj.Sections) {$s.parentNotebook = $bookobj }
$bookobj
}
}
# for site, get the user's MySite. Convert it into a graph URL and get that, expand drives subSites and lists, and add formatting types
elseif ($Site ) {
$response = Invoke-GraphRequest -Uri ($uri + '?$select=mysite')
$uri = $GraphUri + ($response.mysite -replace '^https://(.*?)/(.*)$', '/sites/$1:/$2?expand=drives,lists,sites')
$siteObj = Invoke-GraphRequest $Uri -Exclude '@odata.context', '[email protected]',
'[email protected]', '[email protected]' -As ([MicrosoftGraphSite])
foreach ($l in $siteObj.lists) {
Add-Member -InputObject $l -MemberType NoteProperty -Name SiteID -Value $siteObj.id
}
$siteObj
}
elseif ($Groups -or
$SecurityGroups ) {
if ($SecurityGroups) {$body = '{ "securityEnabledOnly": true }'}
else {$body = '{ "securityEnabledOnly": false }'}
$response = Invoke-GraphRequest -Uri ($uri + '/getMemberGroups') -Method POST -Body $body -ContentType 'application/json'
foreach ($r in $response.value) {
$result += Invoke-GraphRequest -Uri "$GraphUri/directoryObjects/$r"
}
}
elseif ($Manager ) {
$result += Invoke-GraphRequest -Uri ($uri + '/Manager') }
elseif ($DirectReports ) {
$result += Invoke-GraphRequest -Uri ($uri + '/directReports') -All}
elseif ($MemberOf ) {
$result += Invoke-GraphRequest -Uri ($uri + '/MemberOf') -All}
elseif ($TransitiveMemberOf ) {
$result += Invoke-GraphRequest -Uri ($uri + '/TransitiveMemberOf') -All}
else {
foreach ($s in @('ID', 'userPrincipalName', 'displayName')){if ($s -notin $Select) {$Select += $s }}
$result += Invoke-GraphRequest -Uri ($uri + '?$expand=manager&$select=' + ($Select -join ','))
}
}
#if we get a not found error that's propably OK - bail for any other error.
catch {
if ($_.exception.response.statuscode.value__ -eq 404) {
Write-Warning -Message "'Not found' error while getting data for user '$($u.ToString())'"
}
elseif ($_.exception.response.statuscode.value__ -eq 403) {
Write-Warning -Message "'Forbidden' error while getting data for user '$($u.ToString())'. Do you have access to the correct scope?"
}
else {
Write-Progress -Activity 'Getting user information' -Completed
throw $_ ; return
}
}
#endregion
}
foreach ($r in ($result )) {
if ($r.'@odata.type' -match 'directoryRole$') {
#This is a hack so we get role memberships and group memberships laying nicely
$disallowedProperties = $r.keys.where({$_ -notin $script:GroupProperties})
foreach ($p in $disallowedProperties) {$null = $r.remove($p)}
[void]$r.add('GroupTypes','DirectoryRole')
New-Object -Property $r -TypeName ([MicrosoftGraphGroup])
}
elseif ($r.'@odata.type' -match 'group$') {
$disallowedProperties = $r.keys.where({$_ -notin $script:GroupProperties})
foreach ($p in $disallowedProperties) {$null = $r.remove($p)}
New-Object -Property $r -TypeName ([MicrosoftGraphGroup])
}
elseif ($r.'@odata.type' -match 'user$' -or $PSCmdlet.parameterSetName -eq 'None' -or $Select) {
ConvertTo-GraphUser -RawUser $r
}
else {$r}
}
}
end {
Write-Progress -Activity 'Getting user information' -Completed
}
}
function Set-GraphUser {
<#
.Synopsis
Sets properties of a user (the current user by default)
.Example
Set-GraphUser -Birthday "31 march 1965" -Aboutme "Lots to say" -PastProjects "Phoenix","Excalibur" -interests "Photography","F1" -Skills "PowerShell","Active Directory","Networking","Clustering","Excel","SQL","Devops","Server builds","Windows Server","Office 365" -Responsibilities "Design","Implementation","Audit"
Sets the current user, giving lists for projects, interests and skills
.Description
Needs consent to use the User.ReadWrite, User.ReadWrite.All, Directory.ReadWrite.All,
or Directory.AccessAsUser.All scope.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSPossibleIncorrectComparisonWithNull', '', Justification='In this case we want exactly that behaviour')]
[OutputType([Microsoft.Graph.PowerShell.Models.MicrosoftGraphUser])]
[cmdletbinding(SupportsShouldprocess=$true)]
param (
#ID for the user if not the current user
[parameter(Position=0,ValueFromPipeline=$true)]
[ArgumentCompleter([UPNCompleter])]
$UserID = "me",
#A freeform text entry field for the user to describe themselves.
[String]$AboutMe,
#The SMTP address for the user, for example, '[email protected]'
[String]$Mail,
#A list of additional email addresses for the user; for example: ['[email protected]', '[email protected]'].
[String[]]$OtherMails,
#User's mobile phone number
[String]$MobilePhone,
#The telephone numbers for the user. NOTE: Although this is a string collection, only one number can be set for this property
[String[]]$BusinessPhones,
#Url for user's personal site.
[String]$MySite,
#A two letter country code (ISO standard 3166). Required for users that will be assigned licenses due to legal requirement to check for availability of services in countries. Examples include: 'US', 'JP', and 'GB'
[ValidateNotNullOrEmpty()]
[UpperCaseTransformAttribute()]
[ValidateCountryAttribute()]
[string]$UsageLocation,
#The name displayed in the address book for the user. This is usually the combination of the user''s first name, middle initial and last name. This property is required when a user is created and it cannot be cleared during updates.
[ValidateNotNullOrEmpty()]
[string]$DisplayName,
#The given name (first name) of the user.
[Alias('FirstName')]
[string]$GivenName,
#User's last / family name
[Alias('LastName')]
[string]$Surname,
#The user's job title
[string]$JobTitle,
#The name for the department in which the user works.
[string]$Department,
#The office location in the user's place of business.
[string]$OfficeLocation,
# The company name which the user is associated. This property can be useful for describing the company that an external user comes from. The maximum length of the company name is 64 chararcters.
$CompanyName,
#ID or UserPrincipalName of the user's manager
[ArgumentCompleter([UPNCompleter])]
[string]$Manager,
#The employee identifier assigned to the user by the organization
[string]$EmployeeID,
#Captures enterprise worker type: Employee, Contractor, Consultant, Vendor, etc.
[string]$EmployeeType,
#The date and time when the user was hired or will start work in case of a future hire
[datetime]$EmployeeHireDate,
#For an external user invited to the tenant using the invitation API, this property represents the invited user's invitation status. For invited users, the state can be PendingAcceptance or Accepted, or null for all other users.
$ExternalUserState,
#The street address of the user's place of business.
$StreetAddress,
#The city in which the user is located.
$City,
#The state, province or county in the user's address.
$State,
#The country/region in which the user is located; for example, 'US' or 'UK'
$Country,
#The postal code for the user's postal address, specific to the user's country/region. In the United States of America, this attribute contains the ZIP code.
$PostalCode,
#User's birthday as a date. If passing a string it can be "March 31 1965", "31 March 1965", "1965/03/31" or "3/31/1965" - this layout will always be read as US format.
[DateTime]$Birthday,
#List of user's interests
[String[]]$Interests,
#List of user's past projects
[String[]]$PastProjects,
#Path to a .jpg file holding the users photos
[String]$Photo,
#List of user's responsibilities
[String[]]$Responsibilities,
#List of user's Schools
[String[]]$Schools,
#List of user's skills
[String[]]$Skills,
#Set to disable the user account, to re-enable an account use $AccountDisabled:$false
[switch]$AccountDisabled,
#If specified the modified user will be returned
[switch]$PassThru,
#Supresses any confirmation prompt
[Switch]$Force
)
begin {
#things we don't want to put in the JSON body when we send the changes.
$excludedParams = [Cmdlet]::CommonParameters + [Cmdlet]::OptionalCommonParameters + @('Force', 'PassThru', 'UserID', 'AccountDisabled', 'Photo', 'Manager')
$settings = @{}
$returnProps = $Script:DefaultUserProperties
if ($userid -ne $me -and $userid -ne $global:GraphUser -and
$PSBoundparameters['aboutMe', 'birthday', 'hireDate', 'interests', 'mySite', 'pastProjects',
'preferredName', 'responsibilities', 'schools', 'skills'] -ne $null) {
Write-Warning "One or more of the selected properties can only be set by user '$($UserID.ToString())'."
return
}
foreach ($p in $PSBoundparameters.Keys.where({$_ -notin $excludedParams})) {
#turn "Param" into "param" make dates suitable text, and switches booleans
$key = $p.toLower()[0] + $p.Substring(1)
if ($key -notin $returnProps) {$returnProps += $key}
$value = $PSBoundparameters[$p]
if ($value -is [datetime]) {$value = $value.ToString("yyyy-MM-ddT00:00:00Z")} # 'o' for ISO date time may work here
if ($value -is [switch]) {$value = $value -as [bool]}
$settings[$key] = $value
}
if ($PSBoundparameters.ContainsKey('AccountDisabled')) {#allows -accountDisabled:$false
$settings['accountEnabled'] = -not $AccountDisabled
if ($returnProps -notcontains 'accountEnabled') {$returnProps += 'accountEnabled'}
}
if ($settings.count -eq 0 -and -not $Photo -and -not $Manager) {
Write-Warning -Message "Nothing to set"
}
else {
#Don't put the body into webparams which will be used for multiple things
$json = (ConvertTo-Json $settings) -replace '""' , 'null'
Write-Debug $json
}
}
process {
ContextHas -WorkOrSchoolAccount -BreakIfNot
#xxxx todo check scopes User.ReadWrite, User.ReadWrite.All, Directory.ReadWrite.All, or Directory.AccessAsUser.All scope.
if ($UserID -is [string] -and
$UserID -notmatch "^me$|\w@\w|$GUIDRegex" ) {
$UserID = Get-GraphUser $UserID
}
#allow an array of users to be passed.
foreach ($id in $UserID ) {
#region configure the web parameters for changing the user. Allow for filtered objects with an ID or a UPN
$webparams = @{
'Method' = 'PATCH'
'Contenttype' = 'application/json'
}
if ($id -is [string] -and
$id -match "\w@\w|$GUIDRegex" ){
$webparams['uri'] = "$GraphUri/users/$id/" }
elseif ($id -eq "me") {$webparams['uri'] = "$GraphUri/me/" }
elseif ($id.id) {$webparams['uri'] = "$GraphUri/users/$($id.id)/"}
elseif ($id.UserPrincipalName) {$webparams['uri'] = "$GraphUri/users/$($id.UserPrincipalName)/"}
else {Write-Warning "$id does not look like a valid user"; continue}
if ($id -is [string]) {$promptName = $id}
elseif ($id.DisplayName) {$promptName = $id.DisplayName}
elseif ($id.UserPrincipalName) {$promptName = $id.UserPrincipalName}
else {$promptName = $id.ToString() }
#endregion
if ($json -and ($Force -or $Pscmdlet.Shouldprocess($promptName ,'Update User'))){
$null = Invoke-GraphRequest @webparams -Body $json
}
if ($Photo) {
if (-not (Test-Path $Photo) -or $photo -notlike "*.jpg" ) {
Write-Warning "$photo doesn't look like the path to a .jpg file" ; return
}
else {$photoPath = (Resolve-Path $Photo).Path }
$baseUri = $webparams['uri']
$webparams['uri'] = $webparams['uri'] + 'photo/$value'
$webparams['Method'] = 'Put'
$webparams['Contenttype'] = 'image/jpeg'
Write-Debug "Uploading Photo: '$photoPath'"
if ($Force -or $Pscmdlet.Shouldprocess($userID ,'Update User')) {
$null = Invoke-GraphRequest @webparams -InputFilePath $photoPath
}
$webparams['uri'] = $baseUri
}
if ($Manager) {
if ($Manager -is [string] -and $Manager -notmatch "$GUIDRegex|\w@\w") {
$Manager = Get-GraphUser $manager}
if ($Manger.id) {$Manager = $Manager.id}
if ($Manager -isnot [string] -or $Manager -notmatch "$GUIDRegex|\w@\w" ) {
Write-Warning "Could not resolve the manager"
}
else {
$json = ConvertTo-Json @{ '@odata.id' = "$GraphUri/users/$manager" }
Write-Debug $json
$baseUri = $webparams['uri']
$webparams['uri'] = $webparams['uri'] + 'manager/$ref'
$webparams['Method'] = 'Put'
$webparams['Contenttype'] = 'application/json'
if ($Force -or $Pscmdlet.Shouldprocess($userID ,'Update User')) {
$null = Invoke-GraphRequest @webparams -Body $json
}
$webparams['uri'] = $baseUri
}
}
if ($PassThru) {
Invoke-GraphRequest ($webparams.uri + '?$expand=manager&$select=' + ($returnProps -join ',')) | ConvertTo-GraphUser
}
}
}
}
function New-GraphUser {
<#
.synopsis
Creates a new user in Azure Active directory
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '', Justification="False positive and need to support plain text here")]
[cmdletbinding(SupportsShouldProcess=$true)]
param (
#User principal name for the new user. If not specified it can be built by specifying Mail nickname and domain name.
[Parameter(ParameterSetName='DomainFromUPNLast',Mandatory=$true)]
[Parameter(ParameterSetName='DomainFromUPNDisplay',Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[alias("UPN")]
[string]$UserPrincipalName,
#Mail nickname for the new user. If not specified the part of the UPN before the @sign will be used, or using the displayname or first/last name
[Parameter(ParameterSetName='UPNFromDomainLast')]
[Parameter(ParameterSetName='UPNFromDomainDisplay',Mandatory=$true)]
[Parameter(ParameterSetName='DomainFromUPNLast')]
[Parameter(ParameterSetName='DomainFromUPNDisplay')]
[ValidateNotNullOrEmpty()]
[Alias("Nickname")]
[string]$MailNickName,
#Domain for the new user - used to create UPN name if the UPN paramater is not provided
[Parameter(ParameterSetName='UPNFromDomainLast')]
[Parameter(ParameterSetName='UPNFromDomainDisplay')]
[ValidateNotNullOrEmpty()]
[ArgumentCompleter([DomainCompleter])]
[string]$Domain,
#The name displayed in the address book for the user. This is usually the combination of the user''s first name, middle initial and last name. This property is required when a user is created and it cannot be cleared during updates.
[Parameter(ParameterSetName='DomainFromUPNLast')]
[Parameter(ParameterSetName='UPNFromDomainDisplay',Mandatory=$true)]
[Parameter(ParameterSetName='DomainFromUPNDisplay',Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$DisplayName,
#The given name (first name) of the user.
[Parameter(ParameterSetName='UPNFromDomainLast',Mandatory=$true)]
[Parameter(ParameterSetName='DomainFromUPNLast',Mandatory=$true)]
[Alias('FirstName')]
[string]$GivenName,
#User's last / family name
[Parameter(ParameterSetName='UPNFromDomainLast',Mandatory=$true)]
[Parameter(ParameterSetName='DomainFromUPNLast',Mandatory=$true)]
[Alias('LastName')]
[string]$Surname,
#ID or UserPrincipalName of the user's manager
[ArgumentCompleter([UPNCompleter])]
[string]$Manager,
#A two letter country code (ISO standard 3166). Required for users that will be assigned licenses due to legal requirement to check for availability of services in countries. Examples include: 'US', 'JP', and 'GB'
[ValidateNotNullOrEmpty()]
[UpperCaseTransformAttribute()]
[ValidateCountryAttribute()]
[string]$UsageLocation = $Script:DefaultUsageLocation,
[ArgumentCompleter([GroupCompleter])]
$Groups,
[ArgumentCompleter([RoleCompleter])]
$Roles,
[ArgumentCompleter([SkuCompleter])]
$Licenses,
#The initial password for the user. If none is specified one will be generated and output by the command
[string]$Initialpassword,
#If specified the user will not have to change their password on first logon
[switch]$NoPasswordChange,
#If specified the user will need to use Multi-factor authentication when changing their password.
[switch]$ForceMFAPasswordChange,
#Specifies built-in password policies to apply to the user
[ValidateSet('DisableStrongPassword','DisablePasswordExpiration')]
[string[]]$PasswordPolicies,
#A hash table of properties which can be passed as parameters to Set-GraphUser command after the account is created
[hashtable]$SettableProperties,
#A script block specifying how the displayname should be built, by default it is {"$GivenName $Surname"};
[Parameter(ParameterSetName='UPNFromDomainLast')]
[Parameter(ParameterSetName='DomainFromUPNLast')]
[scriptblock]$DisplayNameRule = {"$GivenName $Surname"},
#A script block specifying how the mailnickname should be built, by default it is $GivenName.$Surname with punctuation removed;
[Parameter(ParameterSetName='UPNFromDomainLast')]
[Parameter(ParameterSetName='DomainFromUPNLast')]
[scriptblock]$NickNameRule = {($GivenName -replace '\W','') +'.' + ($Surname -replace '\W','')},
#A script block specifying how to create a password, by default a date between 1800 and 2199 like 10Oct2126 - easy to type and meets complexity rules.
[scriptblock]$PasswordRule = {([datetime]"1/1/1800").AddDays((Get-Random 146000)).tostring("ddMMMyyyy")},
#If specified prevents any confirmation dialog from appearing
[switch]$Force
)
#region we allow the names to be passed flexibly make sure we have what we need
# Accept upn and display name -split upn to make a mailnickname, leave givenname/surname blank
# upn, display name, first and last
# mailnickname, domain, display name [first & last] - create a UPN
# domain, first & last - create a display name, and mail nickname, use the nickname in upn
#re-create any scriptblock passed as a parameter, otherwise variables in this function are out of its scope.
if ($NickNameRule) {$NickNameRule = [scriptblock]::create( $NickNameRule ) }
if ($DisplayNameRule) {$DisplayNameRule = [scriptblock]::create( $DisplayNameRule) }
if ($PasswordRule) {$PasswordRule = [scriptblock]::create( $PasswordRule) }
#if we didn't get a UPN or a mail nickname, make the nickname first, then add the domain to make the UPN
if (-not $UserPrincipalName -and
-not $MailNickName ) {$MailNickName = Invoke-Command -ScriptBlock $NickNameRule
}
#if got a UPN but no nickname, split at @ to get one
elseif ($UserPrincipalName -and
-not $MailNickName) {$MailNickName = $UserPrincipalName -replace '@.*$','' }
#If we didn't get a UPN we should have a domain and a nickname, combine them
if ($MailNickName -and
-not $UserPrincipalName) {
if (-not $Domain) {
$Domain = (Invoke-GraphRequest "$GraphUri/domains?`$select=id,isDefault" -ValueOnly -AsType ([psobject]) |
Where-Object {$_.isdefault} #filter doesn't work in the rest call :-(
).id
}
$UserPrincipalName = "$MailNickName@$Domain" }
#if we didn't get a display name build it
if (-not $DisplayName) {$DisplayName = Invoke-Command -ScriptBlock $DisplayNameRule}
#We should have all 3 by now
if (-not ($DisplayName -and $MailNickName -and $UserPrincipalName -and $UserPrincipalName -match "\w+@\w+")) {
throw "couldn't make sense of those parameters"
}
#A simple way to create one in 100K temporary passwords. You might get 10Oct2126 - easy to type and meets complexity rules.
if (-not $Initialpassword) {$Initialpassword = Invoke-Command -ScriptBlock $PasswordRule
[pscustomobject]@{'DisplayName' = $DisplayName
'UserPrincipalName' = $UserPrincipalName
'Initialpassword' = $Initialpassword}
}
$settings = @{
'accountEnabled' = $true
'displayName' = $DisplayName
'mailNickname' = $MailNickName
'userPrincipalName' = $UserPrincipalName
'usageLocation' = $UsageLocation
'passwordProfile' = @{
'forceChangePasswordNextSignIn' = -not $NoPasswordChange
'password' = $Initialpassword
}
}
if ($ForceMFAPasswordChange) {$settings.passwordProfile['forceChangePasswordNextSignInWithMfa'] = $true}
if ($PasswordPolicies) {$settings['passwordPolicies'] = $PasswordPolicies -join ', '}
if ($GivenName) {$settings['givenName'] = $GivenName }
if ($Surname) {$settings['surname'] = $Surname }
$webparams = @{
'Method' = 'POST'
'Uri' = "$GraphUri/users"
'Contenttype' = 'application/json'
'Body' = (ConvertTo-Json $settings -Depth 5)
'AsType' = [MicrosoftGraphUser]
'ExcludeProperty' = '@odata.context'
}
Write-Debug $webparams.Body
if ($force -or $pscmdlet.ShouldProcess($displayname, 'Create New User')){
try {
$u = Invoke-GraphRequest @webparams
if ($SettableProperties) {
Set-GraphUser -UserID $u.id @SettableProperties -Force
}
if ($manager) {
Set-GraphUser -UserID $u.id -Manager $manager -Force
}
if ($Groups) {
Add-GraphGroupMember -Group $groups -Member $u
}
if ($Roles) {
Grant-GraphDirectoryRole -Role $Roles -Member $u
}
if ($Licenses) {
Grant-GraphLicense -SKUID $Licenses -UserID $u
}
if ($PSBoundParameters['Initialpassword'] ) {return $u }
}
catch {
# xxxx Todo figure out what errors need to be handled (illegal name, duplicate user)
$_
}
}
}
function Reset-GraphUserPassword {
<#
.synopsis
Administrative reset to a given our auto-generated password, defaulting to 'reset at next logon'
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '', Justification="False positive and need to support plain text here")]
[cmdletbinding(SupportsShouldProcess=$true,ConfirmImpact='High')]
param (
#User principal name for the user.
[Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
[ValidateNotNullOrEmpty()]
[alias("UPN")]
[ArgumentCompleter([UPNCompleter])]
[string]$UserPrincipalName,
#The replacement password for the user. If none is specified one will be generated and output by the command
[string]$Initialpassword,
#If specified the user will not have to change their password at their next logon
[switch]$NoPasswordChange,
#If Specified prevents any confirmation dialog from appearing
[switch]$Force
)
if ($UserPrincipalName -notmatch "$Guidregex|\w@\w") {
Write-Warning "$UserPrincipalName does not look like an ID or UPN." ; return
}
if (-not $Initialpassword) {
$Initialpassword = ([datetime]"1/1/1800").AddDays((Get-Random 146000)).tostring("ddMMMyyyy")
}
$webparams = @{
'Method' = 'PATCH'
'Uri' = "$GraphUri/users/$UserPrincipalName/"
'Contenttype' = 'application/json'
'Body' = (ConvertTo-Json @{'passwordProfile' = @{
'password' = $Initialpassword
'forceChangePasswordNextSignIn' = -not $NoPasswordChange}})
}
Write-Debug $webparams.Body
if ($force -or $pscmdlet.ShouldProcess($UserPrincipalName, 'Reset password for user')){
Write-Output "$UserPrincipalName, $Initialpassword"
Invoke-GraphRequest @webparams
}
}
function Remove-GraphUser {
<#
.Synopsis
Deletes a user from Azure Active directory
#>
[cmdletbinding(SupportsShouldprocess=$true,ConfirmImpact='High')]
param (
#ID for the user
[parameter(Position=0,ValueFromPipeline=$true,Mandatory=$true)]
[ArgumentCompleter([UPNCompleter])]
$UserID,
#If specified the user is deleted without a confirmation prompt.
[Switch]$Force
)
process{
ContextHas -WorkOrSchoolAccount -BreakIfNot
#xxxx todo check scopes
if ($userid -is [string] -and $UserID -notmatch "\w@\w|$guidregex") {
$userId = Get-GraphUser $UserID
}
#allow an array of users to be passed.
foreach ($u in $UserID ) {
if ($u.displayName) {$displayname = $u.displayname}
elseif ($u.UserPrincipalName) {$displayName = $u.UserPrincipalName}
else {$displayName = $u}
if ($u.id) {$u = $U.id}
elseif ($u.UserPrincipalName) {$u = $U.UserPrincipalName}
if ($Force -or $pscmdlet.ShouldProcess($displayname,"Delete User")) {
try {
Remove-MgUser_Delete -UserId $u -ErrorAction Stop
}
catch {
if ($_.exception.statuscode.value__ -eq 404) {
Write-Warning -Message "'Not found' error while trying to delete '$displayname'."
}
else {throw $_}
}
}
}
}
}
function Find-GraphPeople {
<#
.Synopsis
Searches people in your inbox / contacts / directory
.Example
Find-GraphPeople -Topic timesheet -First 6
Returns the top 6 results for people you have discussed timesheets with.
.Description
Requires consent to use either the People.Read or the People.Read.All scope
#>
[cmdletbinding(DefaultparameterSetName='Default')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification="Person would be incorrect")]
param (
#Text to use in a 'Topic' Search. Topics are not pre-defined, but inferred using machine learning based on your conversation history (!)
[parameter(ValueFromPipeline=$true,Position=0,parameterSetName='Default',Mandatory=$true)]
[ValidateNotNullOrEmpty()]
$Topic,
#Text to use in a search on name and email address
[parameter(ValueFromPipeline=$true,parameterSetName='Fuzzy',Mandatory=$true)]
[ValidateNotNullOrEmpty()]
$SearchTerm,
#Number of results to return (10 by default)
[ValidateRange(1,1000)]