diff --git a/CHANGELOG.md b/CHANGELOG.md index 82bf2f48..3af67d5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ * [PSGSuite - ChangeLog](#psgsuite---changelog) + * [2.35.0 - 2019-12-29](#2350---2019-12-29) * [2.34.0 - 2019-11-02](#2340---2019-11-02) * [2.33.2 - 2019-10-06](#2332---2019-10-06) * [2.33.1 - 2019-10-06](#2331---2019-10-06) @@ -104,6 +105,21 @@ # PSGSuite - ChangeLog +## 2.35.0 - 2019-12-29 + +* [Issue #216](https://github.com/scrthq/PSGSuite/issues/216) - _Thank you, [@WJurecki](https://github.com/WJurecki)!_ + * Added `Add-GSSheetValues` to use the native `Append()` method instead of `BatchUpdate()` to prevent needing to calculate the last row like you do with `Export-GSSheet`. Since the input for this method has additional options and the output differs from what `Export-GSSheet` outputs, this has been moved to a unique function to prevent introducing breaking changes to `Export-GSSheet`. +* [Issue #221](https://github.com/scrthq/PSGSuite/issues/221) + * Added: `Invoke-GSUserOffboarding` function to wrap common offboarding tasks for ease of access management automation. +* [Issue #248](https://github.com/scrthq/PSGSuite/issues/248) + * Fixed `Get-GSSheetInfo` so it no longer defaults `-IncludeGridData` to `$true` if not specified in `$PSBoundParameters`. +* [Issue #249](https://github.com/scrthq/PSGSuite/issues/249) + * Updated private function `Resolve-Email` with new `IsGroup` switch, then cleaned up all `*-GSGroup*` functions to use it so that Group ID's are respected based on RegEx match. +* [Issue #252](https://github.com/scrthq/PSGSuite/issues/252) + * Added: `Archived` parameter to `Update-GSUser` to enable setting of Archived User licenses. +* Miscellaneous + * Swapped instances of `Get-StoragePath` for `Get-ConfigurationPath` in `Import-SpecificConfiguration` and `Set-PSGSuiteConfig` to avoid alias related issues with PowerShell 4.0 + ## 2.34.0 - 2019-11-02 * [Issue #245](https://github.com/scrthq/PSGSuite/issues/245) + [PR #246](https://github.com/scrthq/PSGSuite/pull/246) - _Thank you, [@devblackops](https://github.com/devblackops)!_ diff --git a/PSGSuite/PSGSuite.psd1 b/PSGSuite/PSGSuite.psd1 index 737e282f..ee0f79d1 100644 --- a/PSGSuite/PSGSuite.psd1 +++ b/PSGSuite/PSGSuite.psd1 @@ -12,7 +12,7 @@ RootModule = 'PSGSuite.psm1' # Version number of this module. - ModuleVersion = '2.34.0' + ModuleVersion = '2.35.0' # ID used to uniquely identify this module GUID = '9d751152-e83e-40bb-a6db-4c329092aaec' diff --git a/PSGSuite/Private/Import-SpecificConfiguration.ps1 b/PSGSuite/Private/Import-SpecificConfiguration.ps1 index 28e847a3..3fa98203 100644 --- a/PSGSuite/Private/Import-SpecificConfiguration.ps1 +++ b/PSGSuite/Private/Import-SpecificConfiguration.ps1 @@ -117,7 +117,7 @@ function Import-SpecificConfiguration { } else { if (!$Scope -or $Scope -eq "Machine") { - $MachinePath = Get-StoragePath @Parameters -Scope Machine + $MachinePath = Get-ConfigurationPath @Parameters -Scope Machine $MachinePath = Join-Path $MachinePath Configuration.psd1 $Machine = if (Test-Path $MachinePath) { Import-Metadata $MachinePath -ErrorAction Ignore -Ordered:$Ordered @@ -129,7 +129,7 @@ function Import-SpecificConfiguration { } if (!$Scope -or $Scope -eq "Enterprise") { - $EnterprisePath = Get-StoragePath @Parameters -Scope Enterprise + $EnterprisePath = Get-ConfigurationPath @Parameters -Scope Enterprise $EnterprisePath = Join-Path $EnterprisePath Configuration.psd1 $Enterprise = if (Test-Path $EnterprisePath) { Import-Metadata $EnterprisePath -ErrorAction Ignore -Ordered:$Ordered @@ -141,7 +141,7 @@ function Import-SpecificConfiguration { } if (!$Scope -or $Scope -eq "User") { - $LocalUserPath = Get-StoragePath @Parameters -Scope User + $LocalUserPath = Get-ConfigurationPath @Parameters -Scope User $LocalUserPath = Join-Path $LocalUserPath Configuration.psd1 $LocalUser = if (Test-Path $LocalUserPath) { Import-Metadata $LocalUserPath -ErrorAction Ignore -Ordered:$Ordered diff --git a/PSGSuite/Private/Resolve-Email.ps1 b/PSGSuite/Private/Resolve-Email.ps1 index 7e45e777..1448016f 100644 --- a/PSGSuite/Private/Resolve-Email.ps1 +++ b/PSGSuite/Private/Resolve-Email.ps1 @@ -3,11 +3,14 @@ function Resolve-Email { Param ( [parameter(Mandatory,Position = 0,ValueFromPipeline,ValueFromPipelineByPropertyName)] [Ref[]] - $Email + $Email, + [parameter()] + [Switch] + $IsGroup ) Process { foreach ($e in $Email) { - if ( -not ($e.value -as [decimal])) { + if (-not $IsGroup -and -not ($e.value -as [decimal])) { if ($e.value -ceq 'me') { $e.value = $Script:PSGSuite.AdminEmail } @@ -15,6 +18,9 @@ function Resolve-Email { $e.value = "$($e.value)@$($Script:PSGSuite.Domain)" } } + elseif ($IsGroup -and $e.value -cnotmatch '^([\w.%+-]+@[a-zA-Z\d.-]+\.[a-zA-Z]{2,}|\d{2}[a-z\d]{13})$') { + $e.value = "$($e.value)@$($Script:PSGSuite.Domain)" + } } } } diff --git a/PSGSuite/Public/Configuration/Set-PSGSuiteConfig.ps1 b/PSGSuite/Public/Configuration/Set-PSGSuiteConfig.ps1 index e431e3ca..65396cfa 100644 --- a/PSGSuite/Public/Configuration/Set-PSGSuiteConfig.ps1 +++ b/PSGSuite/Public/Configuration/Set-PSGSuiteConfig.ps1 @@ -233,7 +233,7 @@ function Set-PSGSuiteConfig { if ($_p12Key) { $configHash["$ConfigName"]['P12Key'] = $_p12Key } - $configHash["$ConfigName"]['ConfigPath'] = (Join-Path $(Get-Module PSGSuite | Get-StoragePath -Scope $Script:ConfigScope) "Configuration.psd1") + $configHash["$ConfigName"]['ConfigPath'] = (Join-Path $(Get-Module PSGSuite | Get-ConfigurationPath -Scope $Script:ConfigScope) "Configuration.psd1") $configHash | Export-Configuration -CompanyName 'SCRT HQ' -Name 'PSGSuite' -Scope $script:ConfigScope if (!$NoImport) { Get-PSGSuiteConfig -ConfigName $ConfigName -Verbose:$false diff --git a/PSGSuite/Public/Groups/Add-GSGroupMember.ps1 b/PSGSuite/Public/Groups/Add-GSGroupMember.ps1 index cc20b487..2d92203d 100644 --- a/PSGSuite/Public/Groups/Add-GSGroupMember.ps1 +++ b/PSGSuite/Public/Groups/Add-GSGroupMember.ps1 @@ -49,15 +49,11 @@ function Add-GSGroupMember { } Process { try { - if ($Identity -notlike "*@*.*") { - $Identity = "$($Identity)@$($Script:PSGSuite.Domain)" - } + Resolve-Email ([ref]$Identity) -IsGroup $groupObj = Get-GSGroup -Group $Identity -Verbose:$false foreach ($U in $Member) { try { - if ($U -notlike "*@*.*") { - $U = "$($U)@$($Script:PSGSuite.Domain)" - } + Resolve-Email ([ref]$U) Write-Verbose "Adding '$U' as a $Role of group '$Identity'" $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.Member' $body.Email = $U diff --git a/PSGSuite/Public/Groups/Add-GSPrincipalGroupMembership.ps1 b/PSGSuite/Public/Groups/Add-GSPrincipalGroupMembership.ps1 index e8b953cd..878f3566 100644 --- a/PSGSuite/Public/Groups/Add-GSPrincipalGroupMembership.ps1 +++ b/PSGSuite/Public/Groups/Add-GSPrincipalGroupMembership.ps1 @@ -49,21 +49,16 @@ function Add-GSPrincipalGroupMembership { } Process { try { - if ($Identity -notlike "*@*.*") { - $Identity = "$($Identity)@$($Script:PSGSuite.Domain)" - } + Resolve-Email ([ref]$Identity) try { foreach ($U in $MemberOf) { $groupObj = Get-GSGroup -Group $U -Verbose:$false - if ($U -notlike "*@*.*") { - $U = "$($U)@$($Script:PSGSuite.Domain)" - } - Write-Verbose "Adding '$Identity' as a $Role of group '$U'" + Write-Verbose "Adding '$Identity' as a $Role of group '$($groupObj.Email)'" $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.Member' $body.Email = $Identity $body.Role = $Role $request = $service.Members.Insert($body,$groupObj.Id) - $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Group' -Value $U -PassThru + $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Group' -Value $groupObj.Email -PassThru } } catch { diff --git a/PSGSuite/Public/Groups/Get-GSGroup.ps1 b/PSGSuite/Public/Groups/Get-GSGroup.ps1 index 214719ed..c8040161 100644 --- a/PSGSuite/Public/Groups/Get-GSGroup.ps1 +++ b/PSGSuite/Public/Groups/Get-GSGroup.ps1 @@ -6,8 +6,8 @@ .DESCRIPTION Gets the specified group's information. Returns the full group list if -Group is excluded. Designed for parity with Get-ADGroup (although Google's API is unable to 'Filter' for groups) - .PARAMETER Group - The list of groups you would like to retrieve info for. If excluded, returns the group list instead + .PARAMETER Identity + The group or list of groups you would like to retrieve info for. If excluded, returns the group list instead .PARAMETER Filter Query string search. Complete documentation is at https://developers.google.com/admin-sdk/directory/v1/guides/search-groups @@ -54,10 +54,10 @@ Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = "Get")] - [Alias("Email")] + [Alias("Email","Group","GroupEmail")] [ValidateNotNullOrEmpty()] [String[]] - $Group, + $Identity, [parameter(Mandatory = $false,ParameterSetName = "ListFilter")] [Alias('Query')] [string] @@ -94,9 +94,9 @@ Process { switch -Regex ($PSCmdlet.ParameterSetName) { Get { - foreach ($G in $Group) { + foreach ($G in $Identity) { try { - Resolve-Email ([ref]$G) + Resolve-Email ([ref]$G) -IsGroup Write-Verbose "Getting group '$G'" $request = $service.Groups.Get($G) if ($Fields) { diff --git a/PSGSuite/Public/Groups/Get-GSGroupAlias.ps1 b/PSGSuite/Public/Groups/Get-GSGroupAlias.ps1 index eee272de..c6ef0dce 100644 --- a/PSGSuite/Public/Groups/Get-GSGroupAlias.ps1 +++ b/PSGSuite/Public/Groups/Get-GSGroupAlias.ps1 @@ -6,11 +6,11 @@ function Get-GSGroupAlias { .DESCRIPTION Gets the specified G SUite Group's aliases - .PARAMETER Group + .PARAMETER Identity The primary email or ID of the group who you are trying to get aliases for. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config. .EXAMPLE - Get-GSGroupAlias -Group hr + Get-GSGroupAlias -Identity hr Gets the list of aliases for the group hr@domain.com #> @@ -19,10 +19,9 @@ function Get-GSGroupAlias { Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] - [Alias("Email")] - [ValidateNotNullOrEmpty()] + [Alias('GroupEmail','Group','Email')] [String[]] - $Group + $Identity ) Begin { $serviceParams = @{ @@ -32,11 +31,9 @@ function Get-GSGroupAlias { $service = New-GoogleService @serviceParams } Process { - foreach ($G in $Group) { + foreach ($G in $Identity) { try { - if ($G -notlike "*@*.*") { - $G = "$($G)@$($Script:PSGSuite.Domain)" - } + Resolve-Email ([ref]$G) -IsGroup Write-Verbose "Getting Alias list for Group '$G'" $request = $service.Groups.Aliases.List($G) $request.Execute() | Select-Object -ExpandProperty AliasesValue diff --git a/PSGSuite/Public/Groups/Get-GSGroupMember.ps1 b/PSGSuite/Public/Groups/Get-GSGroupMember.ps1 index 6f815a2c..76cc7ceb 100644 --- a/PSGSuite/Public/Groups/Get-GSGroupMember.ps1 +++ b/PSGSuite/Public/Groups/Get-GSGroupMember.ps1 @@ -7,7 +7,7 @@ function Get-GSGroupMember { Gets the group member list of a target group. Designed for parity with Get-ADGroupMember .PARAMETER Identity - The email or GroupID of the target group + The email or unique ID of the target group .PARAMETER Member If specified, returns only the information for this member of the target group @@ -63,13 +63,9 @@ function Get-GSGroupMember { Get { foreach ($I in $Identity) { try { - if ($I -notlike "*@*.*") { - $I = "$($I)@$($Script:PSGSuite.Domain)" - } + Resolve-Email ([ref]$I) -IsGroup foreach ($G in $Member) { - if ($G -notlike "*@*.*") { - $G = "$($G)@$($Script:PSGSuite.Domain)" - } + Resolve-Email ([ref]$G) Write-Verbose "Getting member '$G' of group '$I'" $request = $service.Members.Get($I,$G) $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Group' -Value $I -PassThru @@ -88,9 +84,7 @@ function Get-GSGroupMember { List { foreach ($Id in $Identity) { try { - if ($Id -notlike "*@*.*") { - $Id = "$($Id)@$($Script:PSGSuite.Domain)" - } + Resolve-Email ([ref]$Id) -IsGroup $request = $service.Members.List($Id) if ($Limit -gt 0 -and $PageSize -gt $Limit) { Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with first page" -f $PageSize,$Limit) diff --git a/PSGSuite/Public/Groups/Get-GSGroupSettings.ps1 b/PSGSuite/Public/Groups/Get-GSGroupSettings.ps1 index c51ed31f..436e3f32 100644 --- a/PSGSuite/Public/Groups/Get-GSGroupSettings.ps1 +++ b/PSGSuite/Public/Groups/Get-GSGroupSettings.ps1 @@ -7,7 +7,7 @@ function Get-GSGroupSettings { Gets a group's settings .PARAMETER Identity - The email of the group + The email or unique ID of the group. If only the email name-part is passed, the full email will be contstructed using the Domain from the active config @@ -35,8 +35,10 @@ function Get-GSGroupSettings { Process { try { foreach ($G in $Identity) { - if ($G -notlike "*@*.*") { - $G = "$($G)@$($Script:PSGSuite.Domain)" + Resolve-Email ([ref]$G) -IsGroup + if ($G -notmatch '^[\w.%+-]+@[a-zA-Z\d.-]+\.[a-zA-Z]{2,}$') { + Write-Verbose "Getting Group Email for ID '$G' as the Group Settings API only accepts Group Email addresses." + $G = Get-GSGroup -Identity $G -Verbose:$false | Select-Object -ExpandProperty Email } Write-Verbose "Getting settings for group '$G'" $request = $service.Groups.Get($G) diff --git a/PSGSuite/Public/Groups/New-GSGroup.ps1 b/PSGSuite/Public/Groups/New-GSGroup.ps1 index a9efcb04..f60edb8a 100644 --- a/PSGSuite/Public/Groups/New-GSGroup.ps1 +++ b/PSGSuite/Public/Groups/New-GSGroup.ps1 @@ -43,18 +43,13 @@ function New-GSGroup { } Process { try { - if ($Email -notlike "*@*.*") { - $Email = "$($Email)@$($Script:PSGSuite.Domain)" - } + Resolve-Email ([ref]$Email) Write-Verbose "Creating group '$Email'" $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.Group' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) { switch ($prop) { Email { - if ($PSBoundParameters[$prop] -notlike "*@*.*") { - $PSBoundParameters[$prop] = "$($PSBoundParameters[$prop])@$($Script:PSGSuite.Domain)" - } - $body.$prop = $PSBoundParameters[$prop] + $body.$prop = $Email } Default { $body.$prop = $PSBoundParameters[$prop] diff --git a/PSGSuite/Public/Groups/New-GSGroupAlias.ps1 b/PSGSuite/Public/Groups/New-GSGroupAlias.ps1 index ee7a94e7..d99930d6 100644 --- a/PSGSuite/Public/Groups/New-GSGroupAlias.ps1 +++ b/PSGSuite/Public/Groups/New-GSGroupAlias.ps1 @@ -6,26 +6,24 @@ function New-GSGroupAlias { .DESCRIPTION Creates a new alias for a G Suite group - .PARAMETER Group + .PARAMETER Identity The group to create the alias for .PARAMETER Alias The alias or list of aliases to create for the group .EXAMPLE - New-GSGroupAlias -Group humanresources@domain.com -Alias 'hr@domain.com','hrhelp@domain.com' + New-GSGroupAlias -Identity humanresources@domain.com -Alias 'hr@domain.com','hrhelp@domain.com' Creates 2 new aliases for group Human Resources as 'hr@domain.com' and 'hrhelp@domain.com' #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Alias')] [cmdletbinding()] - Param - ( + Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] - [Alias("Email")] - [ValidateNotNullOrEmpty()] + [Alias('GroupEmail','Group','Email')] [String] - $Group, + $Identity, [parameter(Mandatory = $true,Position = 1)] [String[]] $Alias @@ -38,18 +36,14 @@ function New-GSGroupAlias { $service = New-GoogleService @serviceParams } Process { + Resolve-Email ([ref]$Identity) -IsGroup foreach ($A in $Alias) { try { - if ($Group -notlike "*@*.*") { - $Group = "$($Group)@$($Script:PSGSuite.Domain)" - } - if ($A -notlike "*@*.*") { - $A = "$($A)@$($Script:PSGSuite.Domain)" - } - Write-Verbose "Creating alias '$A' for Group '$Group'" + Resolve-Email ([ref]$A) + Write-Verbose "Creating alias '$A' for Group '$Identity'" $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.Alias' $body.AliasValue = $A - $request = $service.Groups.Aliases.Insert($body,$Group) + $request = $service.Groups.Aliases.Insert($body,$Identity) $request.Execute() } catch { diff --git a/PSGSuite/Public/Groups/Remove-GSGroup.ps1 b/PSGSuite/Public/Groups/Remove-GSGroup.ps1 index 3ea92c3a..819544a1 100644 --- a/PSGSuite/Public/Groups/Remove-GSGroup.ps1 +++ b/PSGSuite/Public/Groups/Remove-GSGroup.ps1 @@ -2,21 +2,20 @@ <# .SYNOPSIS Removes a group - + .DESCRIPTION Removes a group - + .PARAMETER Identity The email or unique Id of the group to removed - + .EXAMPLE Remove-GSGroup 'test_group' -Confirm:$false Removes the group 'test_group@domain.com' without asking for confirmation #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] - Param - ( + Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias('GroupEmail','Group','Email')] [String[]] @@ -32,9 +31,7 @@ Process { try { foreach ($G in $Identity) { - if ($G -notlike "*@*.*") { - $G = "$($G)@$($Script:PSGSuite.Domain)" - } + Resolve-Email ([ref]$G) -IsGroup if ($PSCmdlet.ShouldProcess("Removing group '$G'")) { Write-Verbose "Removing group '$G'" $request = $service.Groups.Delete($G) @@ -52,4 +49,4 @@ } } } -} \ No newline at end of file +} diff --git a/PSGSuite/Public/Groups/Remove-GSGroupAlias.ps1 b/PSGSuite/Public/Groups/Remove-GSGroupAlias.ps1 index 36fb0fda..7290f82e 100644 --- a/PSGSuite/Public/Groups/Remove-GSGroupAlias.ps1 +++ b/PSGSuite/Public/Groups/Remove-GSGroupAlias.ps1 @@ -2,29 +2,27 @@ function Remove-GSGroupAlias { <# .SYNOPSIS Removes an alias from a G Suite group - + .DESCRIPTION Removes an alias from a G Suite group - - .PARAMETER Group + + .PARAMETER Identity The group to remove the alias from - + .PARAMETER Alias The alias or list of aliases to remove from the group - + .EXAMPLE - Remove-GSGroupAlias -Group humanresources@domain.com -Alias 'hr@domain.com','hrhelp@domain.com' + Remove-GSGroupAlias -Identity humanresources@domain.com -Alias 'hr@domain.com','hrhelp@domain.com' Removes 2 aliases for group Human Resources: 'hr@domain.com' and 'hrhelp@domain.com' #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] - Param - ( - [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] - [Alias("Email")] - [ValidateNotNullOrEmpty()] + Param ( + [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] + [Alias('GroupEmail','Group','Email')] [String] - $Group, + $Identity, [parameter(Mandatory = $true,Position = 1)] [String[]] $Alias @@ -37,19 +35,15 @@ function Remove-GSGroupAlias { $service = New-GoogleService @serviceParams } Process { + Resolve-Email ([ref]$Identity) -IsGroup foreach ($A in $Alias) { try { - if ($Group -notlike "*@*.*") { - $Group = "$($Group)@$($Script:PSGSuite.Domain)" - } - if ($A -notlike "*@*.*") { - $A = "$($A)@$($Script:PSGSuite.Domain)" - } - if ($PSCmdlet.ShouldProcess("Removing alias '$A' from Group '$Group'")) { - Write-Verbose "Removing alias '$A' from Group '$Group'" - $request = $service.Groups.Aliases.Delete($Group,$A) + Resolve-Email ([ref]$A) + if ($PSCmdlet.ShouldProcess("Removing alias '$A' from Group '$Identity'")) { + Write-Verbose "Removing alias '$A' from Group '$Identity'" + $request = $service.Groups.Aliases.Delete($Identity,$A) $request.Execute() - Write-Verbose "Alias '$A' has been successfully deleted from Group '$Group'" + Write-Verbose "Alias '$A' has been successfully deleted from Group '$Identity'" } } catch { @@ -62,4 +56,4 @@ function Remove-GSGroupAlias { } } } -} \ No newline at end of file +} diff --git a/PSGSuite/Public/Groups/Remove-GSGroupMember.ps1 b/PSGSuite/Public/Groups/Remove-GSGroupMember.ps1 index 39491daa..a710c88a 100644 --- a/PSGSuite/Public/Groups/Remove-GSGroupMember.ps1 +++ b/PSGSuite/Public/Groups/Remove-GSGroupMember.ps1 @@ -38,14 +38,10 @@ function Remove-GSGroupMember { $service = New-GoogleService @serviceParams } Process { - if ($Identity -notlike "*@*.*") { - $Identity = "$($Identity)@$($Script:PSGSuite.Domain)" - } + Resolve-Email ([ref]$Identity) -IsGroup foreach ($G in $Member) { try { - if ($G -notlike "*@*.*") { - $G = "$($G)@$($Script:PSGSuite.Domain)" - } + Resolve-Email ([ref]$G) if ($PSCmdlet.ShouldProcess("Removing member '$G' from group '$Identity'")) { Write-Verbose "Removing member '$G' from group '$Identity'" $request = $service.Members.Delete($Identity,$G) diff --git a/PSGSuite/Public/Groups/Remove-GSPrincipalGroupMembership.ps1 b/PSGSuite/Public/Groups/Remove-GSPrincipalGroupMembership.ps1 index 36bcf939..b1b97773 100644 --- a/PSGSuite/Public/Groups/Remove-GSPrincipalGroupMembership.ps1 +++ b/PSGSuite/Public/Groups/Remove-GSPrincipalGroupMembership.ps1 @@ -2,16 +2,16 @@ function Remove-GSPrincipalGroupMembership { <# .SYNOPSIS Removes the target member from a group or list of groups - + .DESCRIPTION Removes the target member from a group or list of groups - + .PARAMETER Identity The email or unique Id of the member you would like to remove from the group(s) - + .PARAMETER MemberOf The group(s) to remove the member from - + .EXAMPLE Remove-GSPrincipalGroupMembership -Identity 'joe.smith' -MemberOf admins,test_pool @@ -37,19 +37,15 @@ function Remove-GSPrincipalGroupMembership { $service = New-GoogleService @serviceParams } Process { - if ($Identity -notlike "*@*.*") { - $Identity = "$($Identity)@$($Script:PSGSuite.Domain)" - } + Resolve-Email ([ref]$Identity) foreach ($G in $MemberOf) { try { - if ($G -notlike "*@*.*") { - $G = "$($G)@$($Script:PSGSuite.Domain)" - } + Resolve-Email ([ref]$G) -IsGroup if ($PSCmdlet.ShouldProcess("Removing member '$Identity' from group '$G'")) { Write-Verbose "Removing member '$Identity' from group '$G'" $request = $service.Members.Delete($G,$Identity) $request.Execute() - Write-Verbose "Member '$G' has been successfully removed" + Write-Verbose "Member '$Identity' has been successfully removed from group '$G'" } } catch { @@ -62,4 +58,4 @@ function Remove-GSPrincipalGroupMembership { } } } -} \ No newline at end of file +} diff --git a/PSGSuite/Public/Groups/Set-GSGroupSettings.ps1 b/PSGSuite/Public/Groups/Set-GSGroupSettings.ps1 index 26a31ee5..8a2c6b73 100644 --- a/PSGSuite/Public/Groups/Set-GSGroupSettings.ps1 +++ b/PSGSuite/Public/Groups/Set-GSGroupSettings.ps1 @@ -290,9 +290,7 @@ function Set-GSGroupSettings { Process { try { foreach ($G in $Identity) { - if ($G -notlike "*@*.*") { - $G = "$($G)@$($Script:PSGSuite.Domain)" - } + Resolve-Email ([ref]$G) -IsGroup Write-Verbose "Updating settings for group '$G'" $body = New-Object 'Google.Apis.Groupssettings.v1.Data.Groups' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) { diff --git a/PSGSuite/Public/Groups/Test-GSGroupMembership.ps1 b/PSGSuite/Public/Groups/Test-GSGroupMembership.ps1 index b603d20e..364687d2 100644 --- a/PSGSuite/Public/Groups/Test-GSGroupMembership.ps1 +++ b/PSGSuite/Public/Groups/Test-GSGroupMembership.ps1 @@ -21,8 +21,7 @@ function Test-GSGroupMembership { #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.MembersHasMember')] [cmdletbinding()] - Param - ( + Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias('GroupEmail','Group','Email')] [String] @@ -41,9 +40,10 @@ function Test-GSGroupMembership { $service = New-GoogleService @serviceParams } Process { + Resolve-Email ([ref]$Identity) -IsGroup foreach ($mem in $Member) { try { - Resolve-Email ([ref]$Identity),([ref]$mem) + Resolve-Email ([ref]$mem) Write-Verbose "Checking if group '$Identity' has member '$mem'" $request = $service.Members.HasMember($Identity,$mem) $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Group' -Value $Identity -Force -PassThru | Add-Member -MemberType NoteProperty -Name 'Member' -Value $mem -Force -PassThru diff --git a/PSGSuite/Public/Groups/Update-GSGroupMember.ps1 b/PSGSuite/Public/Groups/Update-GSGroupMember.ps1 index 1a112cc1..d4a0cf42 100644 --- a/PSGSuite/Public/Groups/Update-GSGroupMember.ps1 +++ b/PSGSuite/Public/Groups/Update-GSGroupMember.ps1 @@ -6,8 +6,8 @@ function Update-GSGroupMember { .DESCRIPTION Updates a group member's role and/or delivery preference - .PARAMETER GroupEmail - The email or GroupID of the group to update members of + .PARAMETER Identity + The email or unique ID of the group to update members of .PARAMETER Member The member email or list of member emails that you would like to update @@ -37,23 +37,22 @@ function Update-GSGroupMember { #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Member')] [cmdletbinding()] - Param - ( - [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] - [Alias('Group')] + Param ( + [parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] + [Alias('GroupEmail', 'Group')] [String] - $GroupEmail, - [parameter(Mandatory = $true,Position = 1,ValueFromPipelineByPropertyName = $true)] - [Alias("PrimaryEmail","UserKey","Mail","User","UserEmail","Email","Members")] + $Identity, + [parameter(Mandatory = $true, Position = 1, ValueFromPipelineByPropertyName = $true)] + [Alias("PrimaryEmail", "UserKey", "Mail", "User", "UserEmail", "Email", "Members")] [ValidateNotNullOrEmpty()] [String[]] $Member, [parameter(Mandatory = $false)] - [ValidateSet("MEMBER","MANAGER","OWNER")] + [ValidateSet("MEMBER", "MANAGER", "OWNER")] [String] $Role, [parameter(Mandatory = $false)] - [ValidateSet("ALL_MAIL","DAILY","DIGEST","DISABLED","NONE")] + [ValidateSet("ALL_MAIL", "DAILY", "DIGEST", "DISABLED", "NONE")] [String] $DeliverySettings ) @@ -65,27 +64,20 @@ function Update-GSGroupMember { $service = New-GoogleService @serviceParams } Process { - try { - if ($GroupEmail -notlike "*@*.*") { - $GroupEmail = "$($GroupEmail)@$($Script:PSGSuite.Domain)" - } - #$groupObj = Get-GSGroup -Group $Identity -Verbose:$false + Resolve-Email ([ref]$Identity) -IsGroup + foreach ($U in $Member) { try { - foreach ($U in $Member) { - if ($U -notlike "*@*.*") { - $U = "$($U)@$($Script:PSGSuite.Domain)" - } - Write-Verbose "Updating member '$U' of group '$GroupEmail'" - $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.Member' - if ($PSBoundParameters.Keys -contains 'DeliverySettings') { - $body.DeliverySettings = $PSBoundParameters['DeliverySettings'] - } - if ($PSBoundParameters.Keys -contains 'Role') { - $body.Role = $PSBoundParameters['Role'] - } - $request = $service.Members.Patch($body,$GroupEmail,$U) - $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Group' -Value $GroupEmail -PassThru + Resolve-Email ([ref]$U) + Write-Verbose "Updating member '$U' of group '$Identity'" + $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.Member' + if ($PSBoundParameters.Keys -contains 'DeliverySettings') { + $body.DeliverySettings = $PSBoundParameters['DeliverySettings'] + } + if ($PSBoundParameters.Keys -contains 'Role') { + $body.Role = $PSBoundParameters['Role'] } + $request = $service.Members.Patch($body, $Identity, $U) + $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Group' -Value $Identity -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { @@ -96,13 +88,5 @@ function Update-GSGroupMember { } } } - catch { - if ($ErrorActionPreference -eq 'Stop') { - $PSCmdlet.ThrowTerminatingError($_) - } - else { - Write-Error $_ - } - } } } diff --git a/PSGSuite/Public/Groups/Update-GSGroupSettings.ps1 b/PSGSuite/Public/Groups/Update-GSGroupSettings.ps1 index 548e8a87..afdacb2a 100644 --- a/PSGSuite/Public/Groups/Update-GSGroupSettings.ps1 +++ b/PSGSuite/Public/Groups/Update-GSGroupSettings.ps1 @@ -307,8 +307,10 @@ function Update-GSGroupSettings { Process { try { foreach ($G in $Identity) { - if ($G -notlike "*@*.*") { - $G = "$($G)@$($Script:PSGSuite.Domain)" + Resolve-Email ([ref]$G) -IsGroup + if ($G -notmatch '^[\w.%+-]+@[a-zA-Z\d.-]+\.[a-zA-Z]{2,}$') { + Write-Verbose "Getting Group Email for ID '$G' as the Group Settings API only accepts Group Email addresses." + $G = Get-GSGroup -Identity $G -Verbose:$false | Select-Object -ExpandProperty Email } Write-Verbose "Updating settings for group '$G'" $body = New-Object 'Google.Apis.Groupssettings.v1.Data.Groups' diff --git a/PSGSuite/Public/Sheets/Add-GSSheetValues.ps1 b/PSGSuite/Public/Sheets/Add-GSSheetValues.ps1 new file mode 100644 index 00000000..76a9a92d --- /dev/null +++ b/PSGSuite/Public/Sheets/Add-GSSheetValues.ps1 @@ -0,0 +1,266 @@ +function Add-GSSheetValues { + <# + .SYNOPSIS + Append data after a table of data in a sheet. This uses the native `Spreadsheets.Values.Append()` method instead of `BatchUpdate()`. + + .DESCRIPTION + Append data after a table of data in a sheet. This uses the native `Spreadsheets.Values.Append()` method instead of `BatchUpdate()`. See the following link for more information: https://github.com/scrthq/PSGSuite/issues/216 + + .PARAMETER SpreadsheetId + The unique Id of the SpreadSheet to Append data to if updating an existing Sheet + + .PARAMETER NewSheetTitle + The title of the new SpreadSheet to be created + + .PARAMETER Array + Array of objects/strings/ints to append to the SpreadSheet + + .PARAMETER Value + A single value to update 1 cell with. + + .PARAMETER SheetName + The name of the Sheet to add the data to. If excluded, defaults to Sheet Id '0'. If a new SpreadSheet is being created, this is set to 'Sheet1' to prevent error + + .PARAMETER Style + The table style you would like to export the data as + + Available values are: + * "Standard": headers are on Row 1, table rows are added as subsequent rows (Default) + * "Horizontal": headers are on Column A, table rows are added as subsequent columns + + .PARAMETER Range + The input range is used to search for existing data and find a "table" within that range. Values are appended to the next row of the table, starting with the first column of the table. + + .PARAMETER Append + If $true, skips adding headers to the Sheet + + .PARAMETER User + The primary email of the user that had at least Edit rights to the target Sheet + + Defaults to the AdminEmail user + + .PARAMETER ValueInputOption + How the input data should be interpreted + + Available values are: + * "INPUT_VALUE_OPTION_UNSPECIFIED" + * "RAW" + * "USER_ENTERED" + + .PARAMETER InsertDataOption + How the input data should be inserted. + + Available values are: + * "OVERWRITE" + * "INSERTROWS" + + .PARAMETER ResponseValueRenderOption + Determines how values in the response should be rendered. The default render option is FORMATTEDVALUE. + + Available values are: + * "FORMATTEDVALUE" + * "UNFORMATTEDVALUE" + * "FORMULA" + + .PARAMETER ResponseDateTimeRenderOption + Determines how dates, times, and durations in the response should be rendered. This is ignored if responseValueRenderOption is FORMATTEDVALUE. The default dateTime render option is SERIALNUMBER. + + Available values are: + * "SERIALNUMBER" + * "FORMATTEDSTRING" + + .PARAMETER IncludeValuesInResponse + Determines if the update response should include the values of the cells that were updated. By default, responses do not include the updated values + + .PARAMETER Launch + If $true, opens the new SpreadSheet Url in your default browser + + .EXAMPLE + Add-GSSheetValues -SpreadsheetId $sheetId -Array $items -Range 'A:Z' + + Finds the first empty row on the Sheet and appends the $items array (including header row) to it starting at that row. + + .EXAMPLE + Add-GSSheetValues -SpreadsheetId $sheetId -Array $items -Range 'A:Z' -Append + + Finds the first empty row on the Sheet and appends the $items array (excludes header row due to -Append switch) to it starting at that row. + #> + [OutputType('Google.Apis.Sheets.v4.Data.Spreadsheet')] + [cmdletbinding(DefaultParameterSetName = "CreateNewSheetArray")] + Param + ( + [parameter(Mandatory = $true, Position = 0, ParameterSetName = "UseExistingArray")] + [parameter(Mandatory = $true, Position = 0, ParameterSetName = "UseExistingValue")] + [String] + $SpreadsheetId, + [parameter(Mandatory = $false, Position = 0, ParameterSetName = "CreateNewSheetArray")] + [parameter(Mandatory = $false, Position = 0, ParameterSetName = "CreateNewSheetValue")] + [String] + $NewSheetTitle, + [parameter(Mandatory = $true, Position = 1, ValueFromPipeline = $true, ParameterSetName = "UseExistingArray")] + [parameter(Mandatory = $true, Position = 1, ValueFromPipeline = $true, ParameterSetName = "CreateNewSheetArray")] + [object[]] + $Array, + [parameter(Mandatory = $true, Position = 1, ParameterSetName = "UseExistingValue")] + [parameter(Mandatory = $true, Position = 1, ParameterSetName = "CreateNewSheetValue")] + [string] + $Value, + [parameter(Mandatory = $false)] + [String] + $SheetName, + [parameter(Mandatory = $false, ParameterSetName = "UseExistingArray")] + [parameter(Mandatory = $false, ParameterSetName = "CreateNewSheetArray")] + [ValidateSet('Standard', 'Horizontal')] + [String] + $Style = "Standard", + [parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [Alias('SpecifyRange')] + [string] + $Range, + [parameter(Mandatory = $false)] + [switch] + $Append, + [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] + [Alias('Owner', 'PrimaryEmail', 'UserKey', 'Mail')] + [string] + $User = $Script:PSGSuite.AdminEmail, + [parameter(Mandatory = $false)] + [Google.Apis.Sheets.v4.SpreadsheetsResource+ValuesResource+AppendRequest+ValueInputOptionEnum] + $ValueInputOption = [Google.Apis.Sheets.v4.SpreadsheetsResource+ValuesResource+AppendRequest+ValueInputOptionEnum]::RAW, + [parameter(Mandatory = $false)] + [Google.Apis.Sheets.v4.SpreadsheetsResource+ValuesResource+AppendRequest+InsertDataOptionEnum] + $InsertDataOption = [Google.Apis.Sheets.v4.SpreadsheetsResource+ValuesResource+AppendRequest+InsertDataOptionEnum]::OVERWRITE, + [parameter(Mandatory = $false)] + [Google.Apis.Sheets.v4.SpreadsheetsResource+ValuesResource+AppendRequest+ResponseValueRenderOptionEnum] + $ResponseValueRenderOption = [Google.Apis.Sheets.v4.SpreadsheetsResource+ValuesResource+AppendRequest+ResponseValueRenderOptionEnum]::FORMATTEDVALUE, + [parameter(Mandatory = $false)] + [Google.Apis.Sheets.v4.SpreadsheetsResource+ValuesResource+AppendRequest+ResponseDateTimeRenderOptionEnum] + $ResponseDateTimeRenderOption = [Google.Apis.Sheets.v4.SpreadsheetsResource+ValuesResource+AppendRequest+ResponseDateTimeRenderOptionEnum]::FORMATTEDSTRING, + [parameter(Mandatory = $false)] + [Switch] + $IncludeValuesInResponse, + [parameter(Mandatory = $false)] + [Alias('Open')] + [Switch] + $Launch + + ) + Begin { + $values = New-Object 'System.Collections.Generic.List[System.Collections.Generic.IList[Object]]' + } + Process { + if ($User -ceq 'me') { + $User = $Script:PSGSuite.AdminEmail + } + elseif ($User -notlike "*@*.*") { + $User = "$($User)@$($Script:PSGSuite.Domain)" + } + $serviceParams = @{ + Scope = 'https://www.googleapis.com/auth/drive' + ServiceType = 'Google.Apis.Sheets.v4.SheetsService' + User = $User + } + $service = New-GoogleService @serviceParams + try { + if ($Value) { + $finalArray = $([pscustomobject]@{Value = "$Value" }) + $Append = $true + } + else { + if (!$contentType) { + $contentType = $Array[0].PSObject.TypeNames[0] + } + $finalArray = @() + if ($contentType -eq 'System.String' -or $contentType -like "System.Int*") { + $Append = $true + foreach ($item in $Array) { + $finalArray += $([pscustomobject]@{Value = $item }) + } + } + else { + foreach ($item in $Array) { + $finalArray += $item + } + } + } + if (!$Append) { + $propArray = New-Object 'System.Collections.Generic.List[Object]' + $finalArray[0].PSObject.Properties.Name | ForEach-Object { + $propArray.Add($_) + } + $values.Add([System.Collections.Generic.IList[Object]]$propArray) + $Append = $true + } + foreach ($object in $finalArray) { + $valueArray = New-Object 'System.Collections.Generic.List[Object]' + $object.PSobject.Properties.Value | ForEach-Object { + $valueArray.Add($_) + } + $values.Add([System.Collections.Generic.IList[Object]]$valueArray) + } + } + catch { + $PSCmdlet.ThrowTerminatingError($_) + } + } + End { + try { + if ($PSCmdlet.ParameterSetName -like "CreateNewSheet*") { + if ($NewSheetTitle) { + Write-Verbose "Creating new spreadsheet titled: $NewSheetTitle" + } + else { + Write-Verbose "Creating new untitled spreadsheet" + } + $sheet = New-GSSheet -Title $NewSheetTitle -User $User -Verbose:$false + $SpreadsheetId = $sheet.SpreadsheetId + $SpreadsheetUrl = $sheet.SpreadsheetUrl + $SheetName = 'Sheet1' + Write-Verbose "New spreadsheet ID: $SpreadsheetId" + } + else { + $sheet = Get-GSSheetInfo -SpreadsheetId $SpreadsheetId -User $User -Verbose:$false + $SpreadsheetUrl = $sheet.SpreadsheetUrl + } + if ($SheetName) { + if ($Range -like "'*'!*") { + throw "SpecifyRange formatting error! When using the SheetName parameter, please exclude the SheetName when formatting the SpecifyRange value (i.e. 'A1:Z1000')" + } + elseif ($Range) { + $Range = "'$($SheetName)'!$Range" + } + else { + $Range = "$SheetName" + } + } + $body = (New-Object 'Google.Apis.Sheets.v4.Data.ValueRange' -Property @{ + Range = $Range + MajorDimension = "$(if($Style -eq 'Horizontal'){'COLUMNS'}else{'ROWS'})" + Values = [System.Collections.Generic.IList[System.Collections.Generic.IList[Object]]]$values + }) + + $request = $service.Spreadsheets.Values.Append($body, $SpreadsheetId, $Range) + $request.valueInputOption = $ValueInputOption; + $request.insertDataOption = $InsertDataOption; + $request.IncludeValuesInResponse = $IncludeValuesInResponse; + $request.responseValueRenderOption = $ResponseValueRenderOption; + $request.responseDateTimeRenderOption = $ResponseDateTimeRenderOption; + + Write-Verbose "Appending to Range '$Range' on Spreadsheet '$SpreadsheetId' for user '$User'" + $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru | Add-Member -MemberType NoteProperty -Name 'SpreadsheetUrl' -Value $SpreadsheetUrl -PassThru + if ($Launch) { + Write-Verbose "Launching new spreadsheet at $SpreadsheetUrl" + Start-Process $SpreadsheetUrl + } + } + catch { + if ($ErrorActionPreference -eq 'Stop') { + $PSCmdlet.ThrowTerminatingError($_) + } + else { + Write-Error $_ + } + } + } +} diff --git a/PSGSuite/Public/Sheets/Export-GSSheet.ps1 b/PSGSuite/Public/Sheets/Export-GSSheet.ps1 index ee06cb7d..52c4baad 100644 --- a/PSGSuite/Public/Sheets/Export-GSSheet.ps1 +++ b/PSGSuite/Public/Sheets/Export-GSSheet.ps1 @@ -184,7 +184,7 @@ function Export-GSSheet { Write-Verbose "New spreadsheet ID: $SpreadsheetId" } else { - $sheet = Get-GSSheetInfo -SpreadsheetId $SpreadsheetId -User $User -Verbose:$false + $sheet = Get-GSSheetInfo -SpreadsheetId $SpreadsheetId -User $User -IncludeGridData:$false -Verbose:$false $SpreadsheetUrl = $sheet.SpreadsheetUrl } if ($SheetName) { diff --git a/PSGSuite/Public/Sheets/Get-GSSheetInfo.ps1 b/PSGSuite/Public/Sheets/Get-GSSheetInfo.ps1 index f47f317b..eaab38cb 100644 --- a/PSGSuite/Public/Sheets/Get-GSSheetInfo.ps1 +++ b/PSGSuite/Public/Sheets/Get-GSSheetInfo.ps1 @@ -19,7 +19,7 @@ function Get-GSSheetInfo { The specific range of the Sheet to retrieve info for .PARAMETER IncludeGridData - Whether or not to include Grid Data in the response + Whether or not to include Grid Data in the response. Defaults to $false .PARAMETER Fields The fields to return in the response @@ -100,12 +100,9 @@ function Get-GSSheetInfo { if ($Fields) { $request.Fields = "$(($Fields | ForEach-Object {$f = $_;@("namedRanges","properties","sheets","spreadsheetId") | Where-Object {$_ -eq $f}}) -join ",")" } - elseif ($PSBoundParameters.Keys -contains 'IncludeGridData') { + if ($PSBoundParameters.Keys -contains 'IncludeGridData') { $request.IncludeGridData = $IncludeGridData } - else { - $request.IncludeGridData = $true - } Write-Verbose "Getting Spreadsheet Id '$SpreadsheetId' for user '$User'" $response = $request.Execute() if (!$Raw) { diff --git a/PSGSuite/Public/Users/Invoke-GSUserOffboarding.ps1 b/PSGSuite/Public/Users/Invoke-GSUserOffboarding.ps1 new file mode 100644 index 00000000..d16573e9 --- /dev/null +++ b/PSGSuite/Public/Users/Invoke-GSUserOffboarding.ps1 @@ -0,0 +1,158 @@ +function Invoke-GSUserOffboarding { + <# + .SYNOPSIS + Wraps some common offboarding tasks such as random password setting, OAuth token revocation, mobile device removal, and more. + + .DESCRIPTION + Wraps some common offboarding tasks such as random password setting, OAuth token revocation, mobile device removal, and more. + + This function outputs in a log-style, timestamped format that is intended for auditability. + + .PARAMETER User + The User to offboard + + .PARAMETER Options + The tasks you would like to perform on the User. Defaults to the following: 'ClearASPs','ClearOAuthTokens','RemoveMobileDevices','Suspend','SetRandomPassword' + + Available options: + * 'Full' - Performs all of the below tasks + * 'ClearASPs' - Clears Application Specific Passwords + * 'ClearOAuthTokens' - Clears OAuth tokens + * 'RemoveMobileDevices' - Removes Mobile Devices + * 'Suspend' - Suspends the user account + * 'SetRandomPassword' - Sets the user's account to a random password + * 'MoveToOrgUnit' - Moves the user to the DestinationOrgUnit specified + * 'SetLicense' - Sets the user to a different license + + .PARAMETER DestinationOrgUnit + If Options include Full or MoveToOrgUnit, this is the OrgUnit that the user will be moved to. + + .PARAMETER License + The License to set the user to. + + .EXAMPLE + Invoke-GSUserOffboarding -User tom.fields@domain.com -Options Full -DestinationOrgUnit '/Former Employees' + + Performs all of the listed tasks against user Tom Fields, including moving them to the '/Former Employees' OrgUnit and setting them to a VFE license. + + .NOTES + Pull requests welcome for functionality enhancements! + #> + [OutputType('System.String')] + [CmdletBinding(SupportsShouldProcess,ConfirmImpact = "High")] + Param( + [Parameter(Mandatory,Position = 0,ValueFromPipeline,ValueFromPipelineByPropertyName)] + [Alias('PrimaryEmail','Mail')] + [string[]] + $User, + [Parameter()] + [ValidateSet('Full','ClearASPs','ClearOAuthTokens','RemoveMobileDevices','Suspend','SetRandomPassword','MoveToOrgUnit','SetLicense')] + [String[]] + $Options = @('ClearASPs','ClearOAuthTokens','RemoveMobileDevices','Suspend','SetRandomPassword'), + [Parameter()] + [string] + $DestinationOrgUnit, + [Parameter()] + [ValidateSet("G-Suite-Enterprise","Google-Apps-Unlimited","Google-Apps-For-Business","Google-Apps-For-Postini","Google-Apps-Lite","Google-Drive-storage-20GB","Google-Drive-storage-50GB","Google-Drive-storage-200GB","Google-Drive-storage-400GB","Google-Drive-storage-1TB","Google-Drive-storage-2TB","Google-Drive-storage-4TB","Google-Drive-storage-8TB","Google-Drive-storage-16TB","Google-Vault","Google-Vault-Former-Employee","1010020020")] + [string] + $License = "Google-Vault-Former-Employee" + ) + Begin { + function New-RandomPassword { + Param ( + [parameter(Mandatory = $false)] + [int] + $Length = 15 + ) + $ascii = $null + for ($a = 33;$a -le 126;$a++) { + $ascii += ,[char][byte]$a + } + for ($loop = 1; $loop -le $length; $loop++) { + $randomPassword += ($ascii | Get-Random) + } + return ([String]$randomPassword) + } + } + Process { + foreach ($U in $User) { + Resolve-Email ([ref]$U) + if ($PSCmdlet.ShouldProcess("Offboarding user: $U")) { + Write-Verbose "Offboarding user: $U" + "[$(Get-Date -Format o)] Starting offboard of user: $U" + $_user = @{User = $U} + $updateParams = @{Confirm = $false} + foreach ($opt in $options) { + switch -RegEx ($opt) { + '(Full|Suspend)' { + $updateParams['Suspended'] = $true + } + '(Full|SetRandomPassword)' { + $updateParams['Password'] = ConvertTo-SecureString (New-RandomPassword) -AsPlainText -Force + $updateParams['ChangePasswordAtNextLogin'] = $true + } + '(Full|MoveToOrgUnit)' { + if ($PSBoundParameters.ContainsKey('DestinationOrgUnit')) { + $updateParams['OrgUnitPath'] = $PSBoundParameters['DestinationOrgUnit'] + } + else { + throw "No DestinationOrgUnit provided!! Stopping further processing" + exit 1 + } + } + } + } + "[$(Get-Date -Format o)] [$U] Updating user" + Update-GSUser @_user @updateParams | Format-List PrimaryEmail,@{N = "FullName";E = {$_.name.fullName}},Suspended,ChangePasswordAtNextLogin,OrgUnitPath + if ($Options -contains 'Full' -or $Options -contains 'ClearASPs') { + "[$(Get-Date -Format o)] [$U] Retrieving App Specific Passwords to remove" + $ASPs = Get-GSUserASPList @_user + if ($ASPs) { + foreach ($ASP in $ASPs) { + "[$(Get-Date -Format o)] [$U] Revoking ASP for '$($ASP.name)'" + Remove-GSUserASP @_user -CodeID $ASP.codeId -Confirm:$false + } + Remove-Variable ASPs -ErrorAction SilentlyContinue + } + else { + "[$(Get-Date -Format o)] [$U] User has no ASP's to remove!" + } + } + if ($Options -contains 'Full' -or $Options -contains 'ClearOAuthTokens') { + "[$(Get-Date -Format o)] [$U] Retrieving OAuth Tokens to remove" + $Tokens = Get-GSUserTokenList @user + if ($Tokens.clientId) { + foreach ($Token in $Tokens) { + "[$(Get-Date -Format o)] [$U] Revoking OAuth Token for '$($Token.displayText)'" + Remove-GSUserToken @user -ClientID $Token.clientId -Confirm:$false + } + Remove-Variable Tokens -ErrorAction SilentlyContinue + } + else { + "[$(Get-Date -Format o)] [$U] User has no OAuth Tokens to remove!" + } + } + if ($Options -contains 'Full' -or $Options -contains 'RemoveMobileDevices') { + "[$(Get-Date -Format o)] [$U] Retrieving Mobile Devices to remove" + $Mobiles = Get-GSMobileDeviceList @user -Projection BASIC + if ($Mobiles) { + foreach ($Mobile in $Mobiles) { + "[$(Get-Date -Format o)] [$U] Removing Mobile Device '$($Mobile.model)'" + Remove-GSMobileDevice -ResourceID $Mobile.resourceId -Confirm:$false + } + Remove-Variable Mobiles -ErrorAction SilentlyContinue + } + else { + "[$(Get-Date -Format o)] [$U] User has no Mobile Devices to remove!" + } + } + if ($Options -contains 'Full' -or $Options -contains 'SetLicense') { + if ($null -ne $License) { + "[$(Get-Date -Format o)] [$U] Setting user license to: $License" + Set-GSUserLicense @user -License $License | Format-List UserId,ProductId,SkuId + } + } + } + } + } +} diff --git a/PSGSuite/Public/Users/Update-GSUser.ps1 b/PSGSuite/Public/Users/Update-GSUser.ps1 index 9ebae793..dc953f86 100644 --- a/PSGSuite/Public/Users/Update-GSUser.ps1 +++ b/PSGSuite/Public/Users/Update-GSUser.ps1 @@ -102,6 +102,9 @@ function Update-GSUser { Requires confirmation. + .PARAMETER Archived + If true, the user will be assigned an Archived User license. If you do not have sufficient Archived User licenses, you will receive a 500 error with reason of "INSUFFICIENT_ARCHIVED_USER_LICENSES". + .PARAMETER CustomSchemas Custom user attribute values to add to the user's account. This parameter only accepts a hashtable where the keys are Schema Names and the value for each key is another hashtable, i.e.: @@ -207,11 +210,14 @@ function Update-GSUser { [Switch] $IsAdmin, [parameter(Mandatory = $false)] + [Switch] + $Archived, + [parameter(Mandatory = $false)] [ValidateScript( { $hash = $_ foreach ($schemaName in $hash.Keys) { if ($hash[$schemaName].GetType().Name -ne 'Hashtable') { - throw "The CustomSchemas parameter only accepts a hashtable where the value of the top-level keys must also be a hashtable. The key '$schemaName' has a value of type '$($hash[$schemaName].GetType().Name)'" + throw "The CustomSchemas parameter only accepts a hashtable where the value of the top-level values must also be a hashtable. The key '$schemaName' has a value of type '$($hash[$schemaName].GetType().Name)'" $valid = $false } else {