PSGSuite.psm1
Param ( [parameter(Position = 0,ValueFromRemainingArguments = $true)] [AllowNull()] [Byte[]] $EncryptionKey = $null, [parameter(Position = 1)] [AllowNull()] [String] $ConfigName ) $ModuleRoot = $PSScriptRoot New-Variable -Name PSGSuiteKey -Value $EncryptionKey -Scope Global -Force function Convert-Base64 { [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0)] [ValidateSet("NormalString","Base64String","WebSafeBase64String")] [ValidateScript( {if ($_ -eq $To) { throw "The 'From' parameter must not be the same as the 'To' parameter" } else { $true }})] [String] $From, [parameter(Mandatory = $true,Position = 1)] [ValidateSet("NormalString","Base64String","WebSafeBase64String")] [ValidateScript( {if ($_ -eq $From) { throw "The 'To' parameter must not be the same as the 'From' parameter" } else { $true }})] [String] $To, [parameter(Mandatory = $true,Position = 2,ValueFromPipeline = $true)] [String] $String, [parameter(Mandatory = $false)] [String] $OutFile ) if ($From -eq "NormalString") { $String = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($String)) } elseif ($From -eq "WebSafeBase64String") { $String = $String.Replace('_', '/').Replace('-', '+').Replace('|','=') switch ($String.Length % 4) { 2 { $String += "==" } 3 { $String += "=" } } } if ($To -eq "NormalString") { $String = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($String)) } elseif ($To -eq "WebSafeBase64String") { $String = $String.TrimEnd("=").Replace('+', '-').Replace('/', '_'); } if ($OutFile) { $String | Set-Content $OutFile -Force } else { return $String } } function Convert-DateToEpoch { Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true)] [datetime] $Date ) Begin { $UnixEpoch = [timezone]::CurrentTimeZone.ToLocalTime([datetime]'1/1/1970') } Process { $result = (("$(($Date - $UnixEpoch).TotalMilliseconds)" -split "\.") -split "\,")[0] } End { return $result } } function Convert-EpochToDate { Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true)] [string] $EpochString ) Begin { $UnixEpoch = [timezone]::CurrentTimeZone.ToLocalTime([datetime]'1/1/1970') } Process { try { $result = $UnixEpoch.AddSeconds($EpochString) } catch { try { $result = $UnixEpoch.AddMilliseconds($EpochString) } catch { $result = $UnixEpoch.AddTicks($EpochString) } } } End { return $result } } function Get-GSDecryptedConfig { [CmdletBinding()] Param( [parameter(Position = 0, ValueFromPipeline, Mandatory)] [object] $Config, [parameter(Position = 1, Mandatory)] [string] $ConfigName, [parameter(Position = 2)] [string] $ConfigPath ) Process { $Config | Select-Object -Property @( @{l = 'ConfigName'; e = { $ConfigName }} @{l = 'P12KeyPath'; e = { Invoke-GSDecrypt $_.P12KeyPath } } 'P12Key' @{l = 'P12KeyPassword'; e = { Invoke-GSDecrypt $_.P12KeyPassword } } @{l = 'P12KeyObject'; e = { Invoke-GSDecrypt $_.P12KeyObject } } @{l = 'JSONServiceAccountKeyPath'; e = {Invoke-GSDecrypt $_.JSONServiceAccountKeyPath}} @{l = 'JSONServiceAccountKey'; e = {Invoke-GSDecrypt $_.JSONServiceAccountKey}} @{l = 'ClientSecretsPath'; e = { Invoke-GSDecrypt $_.ClientSecretsPath } } @{l = 'ClientSecrets'; e = { Invoke-GSDecrypt $_.ClientSecrets } } @{l = 'AppEmail'; e = { if ($_.JSONServiceAccountKey) { (Invoke-GSDecrypt $_.JSONServiceAccountKey | ConvertFrom-Json).client_email } elseif ($_.AppEmail) { Invoke-GSDecrypt $_.AppEmail } } } @{l = 'AdminEmail'; e = { if ($_.AdminEmail) { Invoke-GSDecrypt $_.AdminEmail } elseif ($_.JSONServiceAccountKey) { (Invoke-GSDecrypt $_.JSONServiceAccountKey | ConvertFrom-Json).client_email } elseif ($_.AppEmail) { Invoke-GSDecrypt $_.AppEmail } } } @{l = 'CustomerID'; e = { Invoke-GSDecrypt $_.CustomerID } } @{l = 'Domain'; e = { Invoke-GSDecrypt $_.Domain } } @{l = 'Preference'; e = { Invoke-GSDecrypt $_.Preference } } @{l = 'ServiceAccountClientID'; e = { if ($_.JSONServiceAccountKey) { (Invoke-GSDecrypt $_.JSONServiceAccountKey | ConvertFrom-Json).client_id } elseif ($_.ServiceAccountClientID) { Invoke-GSDecrypt $_.ServiceAccountClientID } elseif ($_.ClientSecrets) { (Invoke-GSDecrypt $_.ClientSecrets | ConvertFrom-Json).installed.client_id } } } @{l = 'Chat'; e = { $dict = @{ Webhooks = @{ } Spaces = @{ } } foreach ($key in $_.Chat.Webhooks.Keys) { $dict['Webhooks'][$key] = (Invoke-GSDecrypt $_.Chat.Webhooks[$key]) } foreach ($key in $_.Chat.Spaces.Keys) { $dict['Spaces'][$key] = (Invoke-GSDecrypt $_.Chat.Spaces[$key]) } $dict } } @{l = 'ConfigPath'; e = { if ($ConfigPath) {(Resolve-Path $ConfigPath).Path} elseif ($_.ConfigPath) {$_.ConfigPath} else {$null} } } ) } } function Invoke-GSDecrypt { param($String) if ($String -is [System.Security.SecureString]) { [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR( [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR( $String ) ) } elseif ($String -is [ScriptBlock]) { $String.InvokeReturnAsIs() } else { $String } } Function Invoke-GSEncrypt { param($string) if ($string -is [System.String] -and -not [String]::IsNullOrEmpty($String)) { ConvertTo-SecureString -String $string -AsPlainText -Force } else { $string } } function Get-MimeType { Param ( [parameter(Mandatory=$true, ValueFromPipeline=$true,Position = 0)] [System.IO.FileInfo] $File ) $mimeHash = @{ arj = 'application/arj' bmp = 'image/bmp' cab = 'application/cab' csv = 'text/plain' doc = 'application/msword' gif = 'image/gif' htm = 'text/html' html = 'text/html' jpg = 'image/jpeg' js = 'text/js' log = 'text/plain' md = 'text/plain' mp3 = 'audio/mpeg' ods = 'application/vnd.oasis.opendocument.spreadsheet' pdf = 'application/pdf' php = 'application/x-httpd-php' png = 'image/png' rar = 'application/rar' swf = 'application/x-shockwave-flash' tar = 'application/tar' tmpl = 'text/plain' txt = 'text/plain' xls = 'application/vnd.ms-excel' xlsx = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' xml = 'text/xml' zip = 'application/zip' } if ($File.PSIsContainer) { 'application/vnd.google-apps.folder' } elseif ($mime = $mimeHash["$($file.Extension.TrimStart('.'))"]) { $mime } else { 'application/octet-stream' } } function Get-PSGSuiteConfigNames { <# .SYNOPSIS Gets the current config names for tab completion on Switch-PSGSuiteConfig. .DESCRIPTION Gets the current config names for tab completion on Switch-PSGSuiteConfig. #> [cmdletbinding()] Param () $fullConf = Import-SpecificConfiguration -CompanyName 'SCRT HQ' -Name 'PSGSuite' -Scope $Script:ConfigScope $fullConf.Keys | Where-Object {$_ -ne 'DefaultConfig'} } function Get-SafeFileName { [CmdletBinding()] Param ( [parameter(Mandatory,ValueFromPipeline,Position = 0)] [String] $Name ) Process { $Name -replace "[$([RegEx]::Escape("$(([System.IO.Path]::GetInvalidFileNameChars() + [System.IO.Path]::GetInvalidPathChars()) -join '')"))]","_" } } function Import-GoogleSDK { [CmdletBinding()] Param() Process { $lib = Resolve-Path "$($script:ModuleRoot)\lib" $refs = @() $sdkPath = $lib $dlls = Get-ChildItem $sdkPath -Filter "*.dll" $specialGoogleDlls = @('Google.Apis.Auth.dll', 'Google.Apis.Core.dll', 'Google.Apis.dll') $googleCore = ($dlls | Where-Object {$_.Name -eq 'Google.Apis.Core.dll'}) $googleApis = ($dlls | Where-Object {$_.Name -eq 'Google.Apis.dll'}) $googleAuth = ($dlls | Where-Object {$_.Name -eq 'Google.Apis.Auth.dll'}) $googleCoreVersion = [System.Version]((Get-Item $googleCore.FullName).VersionInfo.FileVersion) $batches = @( # Load the non-Google DLLs first... ($dlls | Where-Object {$_.Name -notin $refs -and $_.Name -notmatch '^Google'}) # Then load Google.Apis.Core.dll... $googleCore # Then load Google.Apis.dll... $googleApis # Then load Google.Apis.Auth.dll... $googleAuth # Then load the rest of the Google DLLs ($dlls | Where-Object {$_.Name -notin $refs -and $_.Name -match '^Google' -and $_.Name -notin $specialGoogleDlls}) ) foreach ($batch in $batches) { $batch | ForEach-Object { $sdk = $_.Name try { Write-Verbose "Loading assembly: $($sdk)" # Use Reflection.Assembly.LoadFrom to load assemblies [Reflection.Assembly]::LoadFrom($_.FullName) | Out-Null Write-Verbose "Successfully loaded: $($sdk)" } catch [System.Reflection.ReflectionTypeLoadException] { Write-Error "$($sdk): Unable to load assembly!" Write-Host "Message: $($_.Exception.Message)" Write-Host "StackTrace: $($_.Exception.StackTrace)" Write-Host "LoaderExceptions: $($_.Exception.LoaderExceptions)" } catch { Write-Error "$($sdk): $($_.Exception.Message)" } } } } } function Import-SpecificConfiguration { <# .Synopsis Import the full, layered configuration for the module. Allows for specification of scoped module to load, in case different scopes have different encryption levels .Description Imports the DefaultPath Configuration file, and then imports the Machine, Roaming (enterprise), and local config files, if they exist. Each configuration file is layered on top of the one before (so only needs to set values which are different) .Example $Configuration = Import-Configuration This example shows how to use Import-Configuration in your module to load the cached data .Example $Configuration = Get-Module Configuration | Import-Configuration This example shows how to use Import-Configuration in your module to load data cached for another module #> [CmdletBinding(DefaultParameterSetName = '__CallStack')] param( # A callstack. You should not ever pass this. # It is used to calculate the defaults for all the other parameters. [Parameter(ParameterSetName = "__CallStack")] [System.Management.Automation.CallStackFrame[]]$CallStack = $(Get-PSCallStack), # The Module you're importing configuration for [Parameter(ParameterSetName = "__ModuleInfo", ValueFromPipeline = $true)] [System.Management.Automation.PSModuleInfo]$Module = $( $mi = ($CallStack)[0].InvocationInfo.MyCommand.Module if ($mi -and $mi.ExportedCommands.Count -eq 0) { if ($mi2 = Get-Module $mi.ModuleBase -ListAvailable | Where-Object Name -eq $mi.Name | Where-Object ExportedCommands | Select-Object -First 1) { return $mi2 } } return $mi ), # An optional module qualifier (by default, this is blank) [Parameter(ParameterSetName = "ManualOverride", Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [Alias("Author")] [String]$CompanyName = $( if ($Module) { $Name = $Module.CompanyName -replace "[$([Regex]::Escape(-join[IO.Path]::GetInvalidFileNameChars()))]","_" if ($Name -eq "Unknown" -or -not $Name) { $Name = $Module.Author if ($Name -eq "Unknown" -or -not $Name) { $Name = "AnonymousModules" } } $Name } else { "AnonymousScripts" } ), # The name of the module or script # Will be used in the returned storage path [Parameter(ParameterSetName = "ManualOverride", Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [String]$Name = $(if ($Module) { $Module.Name }), # The full path (including file name) of a default Configuration.psd1 file # By default, this is expected to be in the same folder as your module manifest, or adjacent to your script file [Parameter(ParameterSetName = "ManualOverride", ValueFromPipelineByPropertyName = $true)] [Alias("ModuleBase")] [String]$DefaultPath = $(if ($Module) { Join-Path $Module.ModuleBase Configuration.psd1 }), [Parameter(ParameterSetName = "Path",Mandatory = $true)] [ValidateScript( {Test-Path $_})] [string]$Path, [Parameter(Mandatory = $false)] [ValidateSet("User", "Machine", "Enterprise", $null)] [string]$Scope = $Script:ConfigScope, # The version for saved settings -- if set, will be used in the returned path # NOTE: this is *never* calculated, if you use version numbers, you must manage them on your own [Version]$Version, # If set (and PowerShell version 4 or later) preserve the file order of configuration # This results in the output being an OrderedDictionary instead of Hashtable [Switch]$Ordered ) begin { Write-Verbose "Module Name $Name" } process { if (!$Name) { throw "Could not determine the configuration name. When you are not calling Import-Configuration from a module, you must specify the -Author and -Name parameter" } if (Test-Path $DefaultPath -Type Container) { $DefaultPath = Join-Path $DefaultPath Configuration.psd1 } $Configuration = if (Test-Path $DefaultPath) { Import-Metadata $DefaultPath -ErrorAction Ignore -Ordered:$Ordered Write-Verbose "Default config found: $DefaultPath" } else { @{} } $Parameters = @{ CompanyName = $CompanyName Name = $Name } if ($Version) { $Parameters.Version = $Version } if ($Path) { Write-Verbose "Importing configuration from specific path: $Path" Import-Metadata "$(Resolve-Path $Path)" -ErrorAction Ignore -Ordered:$Ordered } else { if (!$Scope -or $Scope -eq "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 Write-Verbose "Machine config found: $MachinePath" } else { @{} } } if (!$Scope -or $Scope -eq "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 Write-Verbose "Enterprise config found: $EnterprisePath" } else { @{} } } if (!$Scope -or $Scope -eq "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 Write-Verbose "LocalUser config found: $LocalUserPath" } else { @{} } } switch ($Scope) { Machine { Write-Verbose "Importing configuration at scope: $Scope" $Configuration | Update-Object $Machine } Enterprise { Write-Verbose "Importing configuration at scope: $Scope" $Configuration | Update-Object $Enterprise } User { Write-Verbose "Importing configuration at scope: $Scope" $Configuration | Update-Object $LocalUser } Default { Write-Verbose "Importing layered configuration" $Configuration | Update-Object $Machine | Update-Object $Enterprise | Update-Object $LocalUser } } } } } function Get-LicenseSkus { ConvertFrom-Json '{ "1010010001": {"aliases": ["identity", "cloudidentity", "ci"], "displayName": "Cloud Identity", "product": "101001"}, "1010050001": {"aliases": ["identitypremium", "cloudidentitypremium", "cip"], "displayName": "Cloud Identity Premium", "product": "101005"}, "1010020020": {"aliases": ["gae", "gse", "enterprise", "gsuiteenterprise", "gwep", "enterpriseplus"], "displayName": "Google Workspace Enterprise Plus", "product": "Google-Apps"}, "1010020025": {"aliases": ["gwbp", "businessplus"], "displayName": "Google Workspace Business Plus", "product": "Google-Apps"}, "1010020026": {"aliases": ["gwestandard", "enterprisestandard"], "displayName": "Google Workspace Enterprise Standard", "product": "Google-Apps"}, "1010020027": {"aliases": ["gwbstarter", "businessstarter"], "displayName": "Google Workspace Business Starter", "product": "Google-Apps"}, "1010020028": {"aliases": ["gwbstandard", "businessstandard"], "displayName": "Google Workspace Business Standard", "product": "Google-Apps"}, "1010020029": {"aliases": ["gwestarter", "enterprisestarter"], "displayName": "Google Workspace Enterprise Starter", "product": "Google-Apps"}, "1010020030": {"aliases": ["gwfstarter", "frontlinestarter"], "displayName": "Google Workspace Frontline Starter", "product": "Google-Apps"}, "1010020031": {"aliases": ["gwfstandard", "frontlinestandard"], "displayName": "Google Workspace Frontline Standard", "product": "Google-Apps"}, "1010060001": {"aliases": ["gwe", "essentials"], "displayName": "Google Workspace Essentials", "product": "Google-Apps"}, "1010060003": {"aliases": ["gwee", "enterpriseessentials"], "displayName": "Google Workspace Enterprise Essentials", "product": "Google-Apps"}, "1010060005": {"aliases": ["gwep", "essentialsplus"], "displayName": "Google Workspace Essentials Plus", "product": "Google-Apps"}, "1010310002": {"aliases": ["gsefe", "e4e", "gsuiteenterpriseeducation", "gwepl"], "displayName": "Google Workspace for Education Plus - Legacy", "product": "101031"}, "1010310003": {"aliases": ["gsefes", "e4es", "gsuiteenterpriseeducationstudent", "gweplstudent"], "displayName": "Google Workspace for Education Plus - Legacy (Student)", "product": "101031"}, "1010310005": {"aliases": ["gwes", "educationstandard"], "displayName": "Google Workspace for Education Standard", "product": "101031"}, "1010310006": {"aliases": ["gwesstaff", "educationstandardstaff"], "displayName": "Google Workspace for Education Standard (Staff)", "product": "101031"}, "1010310007": {"aliases": ["gwesstudent", "educationstandardstudent"], "displayName": "Google Workspace for Education Standard (Extra Student)", "product": "101031"}, "1010310008": {"aliases": ["gwep", "educationplus"], "displayName": "Google Workspace for Education Plus", "product": "101031"}, "1010310009": {"aliases": ["gwepstaff", "educationplusstaff"], "displayName": "Google Workspace for Education Plus (Staff)", "product": "101031"}, "1010310010": {"aliases": ["gwepstudent", "educationplusstudent"], "displayName": "Google Workspace for Education Plus (Extra Student)", "product": "101031"}, "1010370001": {"aliases": ["gwetlu", "educationteachlearnupgrade"], "displayName": "Google Workspace for Education: Teaching and Learning Upgrade", "product": "101037"}, "1010380001": {"aliases": ["asc", "appsheetcore"], "displayName": "AppSheet Core", "product": "101038"}, "1010380002": {"aliases": ["ases", "appsheetenterprisestandard"], "displayName": "AppSheet Enterprise Standard", "product": "101038"}, "1010380003": {"aliases": ["ases2", "appsheetenterpriseplus"], "displayName": "AppSheet Enterprise Plus", "product": "101038"}, "1010470003": {"aliases": ["geminibusiness"], "displayName": "Gemini Business", "product": "101047"}, "1010470001": {"aliases": ["geminienterprise"], "displayName": "Gemini Enterprise", "product": "101047"}, "1010470006": {"aliases": ["aisecurity"], "displayName": "AI Security", "product": "101047"}, "1010470007": {"aliases": ["aimeetings"], "displayName": "AI Meetings and Messaging", "product": "101047"}, "1010470004": {"aliases": ["geminieducation"], "displayName": "Gemini Education", "product": "101047"}, "1010470005": {"aliases": ["geminieducationpremium"], "displayName": "Gemini Education Premium", "product": "101047"}, "1010340001": {"aliases": ["gseau", "enterprisearchived", "gsuiteenterprisearchived", "gweparchived", "enterpriseplusarchived"], "displayName": "Google Workspace Enterprise Plus - Archived User", "product": "101034"}, "1010340002": {"aliases": ["gsbau", "businessarchived", "gsuitebusinessarchived", "gsba"], "displayName": "G Suite Business - Archived User", "product": "101034"}, "1010340003": {"aliases": ["gwbparchived", "businessplusarchived"], "displayName": "Google Workspace Business Plus - Archived User", "product": "101034"}, "1010340004": {"aliases": ["gwesarchived", "enterprisestandardarchived"], "displayName": "Google Workspace Enterprise Standard - Archived User", "product": "101034"}, "1010340005": {"aliases": ["gwbstarterarchived", "businessstarterarchived"], "displayName": "Google Workspace Business Starter - Archived User", "product": "101034"}, "1010340006": {"aliases": ["gwbstandardarchived", "businessstandardarchived"], "displayName": "Google Workspace Business Standard - Archived User", "product": "101034"}, "Google-Apps-Unlimited": {"aliases": ["gau", "gsb", "unlimited", "gsuitebusiness"], "displayName": "G Suite Business", "product": "Google-Apps"}, "Google-Apps-For-Business": {"aliases": ["gafb", "gafw", "basic", "gsuitebasic"], "displayName": "G Suite Basic", "product": "Google-Apps"}, "Google-Apps-Lite": {"aliases": ["gal", "gsl", "lite", "gsuitelite"], "displayName": "G Suite Lite", "product": "Google-Apps"}, "Google-Apps-For-Postini": {"aliases": ["gams", "postini", "gsuitegams", "gsuitepostini", "gsuitemessagesecurity"], "displayName": "Google Apps Message Security", "product": "Google-Apps"}, "Google-Apps-For-Education": {"aliases": ["gwef", "educationfundamentals"], "displayName": "Google Workspace for Education Fundamentals", "product": "Google-Apps"}, "Google-Vault": {"aliases": ["vault", "googlevault", "gv"], "displayName": "Google Vault", "product": "Google-Vault"}, "Google-Vault-Former-Employee": {"aliases": ["vfe", "googlevaultformeremployee", "gvfe"], "displayName": "Google Vault Former Employee", "product": "Google-Vault"} }' } function Get-LicenseProducts { ConvertFrom-Json '{"101001":"Cloud Identity","101005":"Cloud Identity Premium", "101031":"Google Workspace for Education", "101033":"Google Voice", "101034":"Google Workspace Archived User", "101037":"Google Workspace for Education", "101038":"AppSheet", "Google-Apps":"Google Workspace", "Google-Vault":"Google Vault"}' } function Get-LicenseProductHash { Param ( [Parameter(Position = 0)] [String] $Key ) $_result = @{'Cloud-Identity' = '101001'} $_full = Get-LicenseProducts $_products = $_full.PSObject.Properties.Name | Sort-Object -Unique foreach ($_id in $_products) { $_friendlyname = $_full.$_id -replace '\W+','-' $_result[$_id] = $_id $_result[$_friendlyname] = $_id } if ($Key) { $_result[$Key] } else { $_result } } function Get-LicenseSkuHash { Param ( [Parameter(Position = 0)] [String] $Key ) $_result = @{} $_full = Get-LicenseSkus $_licenses = $_full.PSObject.Properties.Name | Sort-Object -Unique foreach ($_sku in $_licenses) { $_displayName = $_full.$_sku.displayName -replace '\W+','-' $_result[$_displayName] = $_sku foreach ($_alias in $_full.$_sku.aliases) { $_result[$_alias] = $_sku } } if ($Key) { $_result[$Key] } else { $_result } } function Get-LicenseSkuToProductHash { Param ( [Parameter(Position = 0)] [String] $Key ) $_result = @{} $_full = Get-LicenseSkus $_licenses = $_full.PSObject.Properties.Name | Sort-Object -Unique foreach ($_sku in $_licenses) { $_displayName = $_full.$_sku.displayName -replace '\W+','-' $_result[$_sku] = $_full.$_sku.product $_result[$_displayName] = $_full.$_sku.product foreach ($_alias in $_full.$_sku.aliases) { $_result[$_alias] = $_full.$_sku.product } } if ($Key) { $_result[$Key] } else { $_result } } function Get-LicenseSkuFromDisplayName { Param ( [Parameter(Position = 0)] [String] $Key ) $_result = @{} $_full = Get-LicenseSkus $_licenses = $_full.PSObject.Properties.Name | Sort-Object -Unique foreach ($_sku in $_licenses) { $_displayName = $_full.$_sku.displayName -replace '\W+','-' $_result[$_displayName] = $_sku } if ($Key) { $_result[$Key] } else { $_result } } function Get-LicenseProductFromDisplayName { Param ( [Parameter(Position = 0)] [String] $Key ) $_result = @{'Cloud-Identity' = '101001'} $_full = Get-LicenseProducts $_products = $_full.PSObject.Properties.Name | Sort-Object -Unique foreach ($_id in $_products) { $_friendlyname = $_full.$_id -replace '\W+','-' $_result[$_friendlyname] = $_id } if ($Key) { $_result[$Key] } else { $_result } } function Move-GSChromeOSDevice { <# .SYNOPSIS Moves a ChromeOS device to a new OrgUnit .DESCRIPTION Moves a ChromeOS device to a new OrgUnit .PARAMETER ResourceId The unique ID of the device you would like to move. .PARAMETER OrgUnitPath Full path of the target organizational unit or its ID .EXAMPLE Move-GSChromeOSDevice -ResourceId 'AFiQxQ8Qgd-rouSmcd2UnuvhYV__WXdacTgJhPEA1QoQJrK1hYbKJXm-8JFlhZOjBF4aVbhleS2FVQk5lI069K2GULpteTlLVpKLJFSLSL' -OrgUnitPath '/Corp' Moves the ChromeOS device specified to the /Corp OrgUnit #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.ChromeOSDevice')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias('Id','Device','DeviceId')] [String[]] $ResourceId, [parameter(Mandatory = $true,Position = 1)] [String] $OrgUnitPath ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.device.chromeos' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams -Verbose:$false $customerId = if ($Script:PSGSuite.CustomerID) { $Script:PSGSuite.CustomerID } else { "my_customer" } $toMove = New-Object 'System.Collections.Generic.List[string]' } Process { $ResourceId | ForEach-Object { $toMove.Add($_) } } End { try { Write-Verbose "Moving the following Chrome OS Devices to OrgUnitPath '$OrgUnitPath':`n - $($toMove -join "`n - ")" $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.ChromeOsMoveDevicesToOu' -Property @{ DeviceIds = $toMove } $request = $service.Chromeosdevices.MoveDevicesToOu($body,$customerId,$OrgUnitPath) $request.Execute() Write-Verbose "The Chrome OS devices were successfully moved." } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } function New-MimeMessage { [cmdletbinding()] Param ( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string[]] $To, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $From, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Subject, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Body, [parameter(Mandatory = $false)] [string[]] $CC, [parameter(Mandatory = $false)] [string[]] $BCC, [parameter(Mandatory = $false)] [ValidateScript( {Test-Path $_})] [string[]] $Attachment, [parameter(Mandatory = $false)] [switch] $BodyAsHtml, [parameter(Mandatory = $false)] [switch] $ReturnConstructedMessage ) $message = [MimeKit.MimeMessage]::new() $message.From.Add($From) $message.Subject = $Subject foreach ($T in $To) { $message.To.Add($T) } if ($CC) { foreach ($C in $CC) { $message.Cc.Add($C) } } if ($BCC) { foreach ($B in $BCC) { $message.Bcc.Add($B) } } if ($BodyAsHtml) { $TextPart = [MimeKit.TextPart]::new("html") } else { $TextPart = [MimeKit.TextPart]::new("plain") } $TextPart.Text = $Body if ($Attachment) { [System.Reflection.Assembly]::LoadWithPartialName('System.IO') | Out-Null $Multipart = [MimeKit.Multipart]::new("mixed") $Multipart.Add($TextPart) foreach ($Attach in $Attachment) { $MimeType = (Get-MimeType -File $Attach) -split "/" $MimePart = [MimeKit.MimePart]::new($MimeType[0], $MimeType[1]) $MimePart.ContentObject = [MimeKit.ContentObject]::new([IO.File]::OpenRead($Attach), [MimeKit.ContentEncoding]::Default) $MimePart.ContentDisposition = [MimeKit.ContentDisposition]::new([MimeKit.ContentDisposition]::Attachment) $MimePart.ContentTransferEncoding = [MimeKit.ContentEncoding]::Base64 $MimePart.FileName = [IO.Path]::GetFileName($Attach) $Multipart.Add($MimePart) } $message.Body = $Multipart } else { $message.Body = $TextPart } if ($ReturnConstructedMessage) { return $message.ToString() } else { return $message } } function Out-TruncatedString { [CmdletBinding()] param ( [Parameter(Position = 0, ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [string] $String, [Parameter(Position = 1)] [ValidateRange(0,[int32]::MaxValue)] [int] $Length = 0 ) $outString = $String if ($Length -gt 0) { if ($String.Length -gt $Length) { $outString = $String.Substring(0,($Length - 3)) + '...' } } Write-Output $outString } function Read-MimeMessage { [cmdletbinding(DefaultParameterSetName = "String")] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ParameterSetName = "String")] [ValidateNotNullOrEmpty()] [string[]] $String, [parameter(Mandatory = $true,ValueFromPipeline = $true,ParameterSetName = "EmlFile")] [ValidateScript( {Test-Path $_})] [string[]] $EmlFile ) Process { switch ($PSCmdlet.ParameterSetName) { String { foreach ($str in $String) { $stream = [System.IO.MemoryStream]::new([Text.Encoding]::UTF8.GetBytes($str)) [MimeKit.MimeMessage]::Load($stream) $stream.Dispose() } } EmlFile { foreach ($str in $EmlFile) { [MimeKit.MimeMessage]::Load($str) } } } } } function Read-Prompt { [CmdletBinding()] Param ( [parameter(Mandatory = $true,Position = 0)] $Options, [parameter(Mandatory = $false)] [String] $Title = "Picky Choosy Time", [parameter(Mandatory = $false)] [String] $Message = "Which do you prefer?", [parameter(Mandatory = $false)] [Int] $Default = 0 ) Process { $opt = @() foreach ($option in $Options) { switch ($option.GetType().Name) { Hashtable { foreach ($key in $option.Keys) { $opt += New-Object System.Management.Automation.Host.ChoiceDescription "$($key)","$($option[$key])" } } String { $opt += New-Object System.Management.Automation.Host.ChoiceDescription "$option",$null } } } $choices = [System.Management.Automation.Host.ChoiceDescription[]] $opt $answer = $host.ui.PromptForChoice($Title, $Message, $choices, $Default) $choices[$answer].Label -replace "&" } } function Resolve-Email { [CmdletBinding()] Param ( [parameter(Mandatory,Position = 0,ValueFromPipeline,ValueFromPipelineByPropertyName)] [Ref[]] $Email, [parameter()] [Switch] $IsGroup ) Process { foreach ($e in $Email) { if (-not $IsGroup -and -not ($e.value -as [decimal])) { if ($e.value -ceq 'me') { $e.value = $Script:PSGSuite.AdminEmail } elseif ($e.value -notlike "*@*.*") { $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)" } } } } function Test-FileLock { param ( [parameter(Mandatory = $false)] [string] $Path ) if ($Path) { $oFile = New-Object System.IO.FileInfo $Path if ((Test-Path -Path $Path) -eq $false) { return $false } try { $oStream = $oFile.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None) if ($oStream) { $oStream.Close() } return $false } catch { return $true } } } function ThrowTerm { Param ( [parameter(Mandatory = $true,Position = 0)] [String] $Message ) New-Object System.Management.Automation.ErrorRecord( (New-Object Exception $Message), 'PowerShell.Module.Error', [System.Management.Automation.ErrorCategory]::OperationStopped, $null ) } function Write-InlineProgress { <# .SYNOPSIS Display an inline progress bar in the PowerShell console. .DESCRIPTION Display an inline progress bar in the PowerShell console. .NOTES Be sure to always call the function with either the -Stop or -Completed switch after the progress bar is finished. Be sure to NOT output anything while the progress bar is updating - or it WILL "break"! This function will not work when run in PowerShell ISE. Author: Øyvind Kallstad Date: 28.04.2016 Version: 1.0 .LINK https://communary.wordpress.com/ #> [CmdletBinding(DefaultParameterSetName = 'normal')] param ( # Describe the activity being performed. [Parameter(Position = 0, ParameterSetName = 'normal')] [Parameter(ParameterSetName = 'completed')] [string] $Activity, # Minimum padding for the activity text. When set to 0 (default) the size of the activity text # is automatically adjusted based on the window width. [Parameter(ParameterSetName = 'normal')] [Parameter(ParameterSetName = 'completed')] [ValidateRange(0,[int]::MaxValue)] [int] $ActivityPadding = 0, # Display seconds remaining. [Parameter(ParameterSetName = 'normal')] [Parameter(ParameterSetName = 'completed')] [ValidateRange(0,[int]::MaxValue)] [int] $SecondsRemaining, # Display seconds elapsed. [Parameter(ParameterSetName = 'normal')] [Parameter(ParameterSetName = 'completed')] [ValidateRange(0,[int]::MaxValue)] [int] $SecondsElapsed, # Define the percent complete value for the progress bar. [Parameter(ParameterSetName = 'normal')] [ValidateRange(0,100)] [int] $PercentComplete, # Display the percent complete. # Note! If the window width is below 40 the percent value will not be displayed. [Parameter(ParameterSetName = 'normal')] [Parameter(ParameterSetName = 'completed')] [switch] $ShowPercent = $true, # Stop without any update to the progress bar. [Parameter(ParameterSetName = 'stop')] [switch] $Stop, # Output last progress result. [Parameter(ParameterSetName = 'stop')] [switch] $OutputLastProgress, # Stop the progress bar with a final update. [Parameter(ParameterSetName = 'completed')] [switch] $Completed, # Customize the progress character. [Parameter(ParameterSetName = 'normal')] [Parameter(ParameterSetName = 'completed')] [ValidateLength(1,1)] [ValidateNotNull()] [string] $ProgressCharacter = '#', # Customize the progress fill character. [Parameter(ParameterSetName = 'normal')] [Parameter(ParameterSetName = 'completed')] [ValidateLength(1,1)] [ValidateNotNull()] [string] $ProgressFillCharacter = '#', # Customize the fill character. [Parameter(ParameterSetName = 'normal')] [Parameter(ParameterSetName = 'completed')] [ValidateLength(1,1)] [ValidateNotNull()] [string] $ProgressFill = '.', # Customize the bracket before the progress bar. [Parameter(ParameterSetName = 'normal')] [Parameter(ParameterSetName = 'completed')] [ValidateLength(0,1)] [string] $BarBracketStart = '[', # Customize the bracket after the progress bar. [Parameter(ParameterSetName = 'normal')] [Parameter(ParameterSetName = 'completed')] [ValidateLength(0,1)] [string] $BarBracketEnd = ']', # Use Write-Output instead of Console.Write # If you want to support transcripts, you need to use this parameter on the last update of the # progress bar so that it will be written to the transcript file. The same goes for any error handling if # you want the last status of the progress bar to be written to the transcript. [Parameter(ParameterSetName = 'normal')] [Parameter(ParameterSetName = 'completed')] [switch] $UseWriteOutput ) # this function only works when run from the console if ($Host.Name -notlike '*ISE*') { if ($Stop) { if ($OutputLastProgress) { Write-Host (($script:lastProgressString).ToString()) -NoNewline } else { Remove-Variable -Name 'lastProgressString' -Scope 'Script' -ErrorAction SilentlyContinue [console]::WriteLine() } try { [System.Console]::CursorVisible = $true } catch { if ($Error[0].Exception.Message -eq 'Exception setting "CursorVisible": "The handle is invalid."') { $Global:Error.Remove($Global:Error[0]) } } } else { if ($Completed) { $PercentComplete = 100 if ($PSBoundParameters.ContainsKey('SecondsRemaining')) { # have to force it to 0 or it will display the last value before it finished $SecondsRemaining = 0 } } # if the buffer if full, we need to resize it to make sure that the progress bar don't break if (($host.UI.RawUI.CursorPosition.y + 1) -ge ($host.UI.RawUI.BufferSize.Height)) { $size = New-Object System.Management.Automation.Host.Size(($host.UI.RawUI.BufferSize.Width), (($host.UI.RawUI.BufferSize.Height + 1000))) $host.UI.RawUI.BufferSize = $size } $cursorPosition = $host.UI.RawUI.CursorPosition #$cursorPositionY = $host.UI.RawUI.CursorPosition.Y try { [System.Console]::CursorVisible = $false } catch { if ($Error[0].Exception.Message -eq 'Exception setting "CursorVisible": "The handle is invalid."') { $Global:Error.Remove($Global:Error[0]) } } $windowWidth = [console]::WindowWidth # if screen is very small, don't display the percent if ($windowWidth -le 40) {$ShowPercent = $false} # calculate the size of the activity part of the output string if ($ActivityPadding -eq 0) { $activityPart = [math]::Floor($windowWidth / 4) } else { $activityPart = $ActivityPadding } # if activity string is longer than the allocated part length, truncate it if ($Activity.Length -gt $activityPart) { $Activity = Out-TruncatedString -String $Activity -Length $activityPart } $progressString = New-Object System.Text.StringBuilder -ArgumentList $windowWidth # add activity text to the progress string [void]$progressString.Append("$($Activity.PadRight($ActivityPart, ' ')) ") # add seconds elapsed to the progress string if ($PSBoundParameters.ContainsKey('SecondsElapsed')) { [void]$progressString.Append([timespan]::FromSeconds($SecondsElapsed).ToString() + ' ') } # add seconds remaining to the progress string if ($PSBoundParameters.ContainsKey('SecondsRemaining')) { [void]$progressString.Append([timespan]::FromSeconds($SecondsRemaining).ToString() + ' ') } # add the start bracket for the progress bar to the progress string [void]$progressString.Append($BarBracketStart) # calculate the width of the progress bar # the 5 is to account for the space of the percent information if ($ShowPercent) { $progressBarWidth = $windowWidth - (($progressString.Length) + 5) } else { $progressBarWidth = $windowWidth - ($progressString.Length) + 1 } # add one to the progress bar width if no end bracket is used if (-not ($BarBracketEnd)) { $progressBarWidth++ } # calculate the bar character percentage and how much of the bar is filled and how much is not filled $barCharacterInPercent = ($progressBarWidth - 2) / 100 $barProgressed = [math]::Floor($PercentComplete * $barCharacterInPercent) $barNotProgressed = ($progressBarWidth - 2) - $barProgressed # add the progress bar to progress string if ($barProgressed -gt 0) { if ($barNotProgressed -gt 0) { [void]$progressString.Append(($ProgressFillCharacter * ($barProgressed - 1))) [void]$progressString.Append($ProgressCharacter) } else { [void]$progressString.Append(($ProgressFillCharacter * $barProgressed)) } } [void]$progressString.Append("$($ProgressFill * $barNotProgressed)$($BarBracketEnd)") # add the percent complete to the progress string if ($ShowPercent) { [void]$progressString.Append(" $($PercentComplete.ToString().PadLeft(3, ' '))% ") } # if not already present, create a string builder to hold the last progress string if (-not ($script:lastProgressString)) { $script:lastProgressString = New-Object System.Text.StringBuilder($windowWidth) } # only update the progress string if it's different from the last (optimization) if (-not($script:lastProgressString.ToString() -eq $progressString.ToString())) { if ($UseWriteOutput) { Write-Output ($progressString.ToString()) } else { [console]::Write(($progressString.ToString())) } $host.UI.RawUI.CursorPosition = $cursorPosition #$host.UI.RawUI.CursorPosition = 0, $cursorPositionY [void]$script:lastProgressString.Clear() [void]$script:lastProgressString.Append($progressString.ToString()) } if ($Completed) { # do some clean-up and jump to the next line Remove-Variable -Name 'lastProgressString' -Scope 'Script' -ErrorAction SilentlyContinue try { [System.Console]::CursorVisible = $true } catch { if ($Error[0].Exception.Message -eq 'Exception setting "CursorVisible": "The handle is invalid."') { $Global:Error.Remove($Global:Error[0]) } } [console]::WriteLine() } } } else { Write-Warning 'This function is not compatible with PowerShell ISE.' } } function Get-GSOrganizationalUnitListPrivate { [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0)] [Alias('OrgUnitPath','BaseOrgUnitPath')] [String] $SearchBase, [parameter(Mandatory = $false)] [Alias('Type')] [ValidateSet('Subtree','OneLevel','All','Children')] [String] $SearchScope = 'All' ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.orgunit' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { Write-Verbose "Getting all Organizational Units" $request = $service.Orgunits.List($Script:PSGSuite.CustomerId) $request.Type = switch ($SearchScope) { Subtree { 'All' } OneLevel { 'Children' } default { $SearchScope } } if ($SearchBase) { $request.OrgUnitPath = $SearchBase } $request.Execute() | Select-Object -ExpandProperty OrganizationUnits } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } function Get-GSResourceListPrivate { [CmdletBinding()] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [ValidateSet('Calendars','Buildings','Features')] [String[]] $Resource = @('Calendars','Buildings','Features'), [parameter(Mandatory = $false)] [Alias('Query')] [String[]] $Filter, [parameter(Mandatory = $false)] [String[]] $OrderBy, [parameter(Mandatory = $false)] [ValidateRange(1,500)] [Alias("MaxResults")] [Int] $PageSize = "500" ) Begin { if ($MyInvocation.InvocationName -eq 'Get-GSCalendarResourceList') { $Resource = 'Calendars' } $propHash = @{ Calendars = 'Items' Buildings = 'BuildingsValue' Features = 'FeaturesValue' } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.resource.calendar' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { foreach ($R in $Resource) { $request = $service.Resources.$R.List($(if($Script:PSGSuite.CustomerID) {$Script:PSGSuite.CustomerID}else {'my_customer'})) if ($R -eq 'Calendars') { if ($PageSize) { $request.MaxResults = $PageSize } if ($OrderBy) { $request.OrderBy = "$($OrderBy -join ", ")" } if ($Filter) { if ($Filter -eq '*') { $Filter = "" } else { $Filter = "$($Filter -join " ")" } $Filter = $Filter -replace " -eq ","=" -replace " -like ",":" -replace " -match ",":" -replace " -contains ",":" -creplace "'True'","True" -creplace "'False'","False" $request.Query = $Filter.Trim() Write-Verbose "Getting Resource $R matching filter: `"$($Filter.Trim())`"" } else { Write-Verbose "Getting all Resource $R" } } else { Write-Verbose "Getting all Resource $R" } [int]$i = 1 do { $result = $request.Execute() $result.$($propHash[$R]) | ForEach-Object { $obj = $_ $_Id = switch ($R) { Calendars { $obj.ResourceId } Buildings { $obj.BuildingId } Features { $obj.Name } } $_ | Add-Member -MemberType NoteProperty -Name 'Id' -Value $_Id -PassThru | Add-Member -MemberType NoteProperty -Name 'Resource' -Value $R -PassThru | Add-Member -MemberType ScriptMethod -Name ToString -Value {$this.Id} -PassThru -Force } if ($result.NextPageToken) { $request.PageToken = $result.NextPageToken } [int]$retrieved = ($i + $result.$($propHash[$R]).Count) - 1 Write-Verbose "Retrieved $retrieved resources..." [int]$i = $i + $result.$($propHash[$R]).Count } until (!$result.NextPageToken) } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } function Get-GSShortUrlListPrivate { [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail","Email")] [ValidateNotNullOrEmpty()] [String[]] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory=$false)] [ValidateSet("Full","Analytics_Clicks")] [string] $Projection = "Full" ) Process { foreach ($U in $User) { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/urlshortener' ServiceType = 'Google.Apis.Urlshortener.v1.UrlshortenerService' User = "$U" } $service = New-GoogleService @serviceParams try { Write-Verbose "Getting Short Url list for User '$U'" $request = $service.Url.List() $result = $request.Execute() if ($null -ne $result.Items) { $result.Items | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } function Get-GSUserASPListPrivate { [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String[]] $User = $Script:PSGSuite.AdminEmail ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.user.security' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { foreach ($U in $User) { try { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } Write-Verbose "Getting ASP list for User '$U'" $request = $service.Asps.List($U) $result = $request.Execute() if ($null -ne $result.Items) { $result.Items | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } function Get-GSUserSchemaListPrivate { [cmdletbinding()] Param( ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.userschema' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { $request = $service.Schemas.List($Script:PSGSuite.CustomerId) $result = $request.Execute() if ($null -ne $result.SchemasValue) { $result.SchemasValue } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } function Get-GSUserTokenListPrivate { [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String[]] $User = $Script:PSGSuite.AdminEmail ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.user.security' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { foreach ($U in $User) { try { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } Write-Verbose "Getting Token list for User '$U'" $request = $service.Tokens.List($U) $result = $request.Execute() if ($null -ne $result.Items) { $result.Items | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } function Clear-PSGSuiteServiceCache { <# .SYNOPSIS Clears the dictionary of cached service objects created with New-GoogleService. .DESCRIPTION Clears the dictionary of cached service objects created with New-GoogleService. .EXAMPLE Clear-PSGSuiteServiceCache .LINK https://psgsuite.io/Function%20Help/Authentication/Clear-PSGSuiteServiceCache/ #> [CmdletBinding()] Param () Begin{ if (-not $script:_PSGSuiteSessions) { $script:_PSGSuiteSessions = @{} } $toRemove = @() } Process { if (-not $script:_PSGSuiteSessions.Keys.Count) { Write-Verbose "There are no current cached sessions to clear!" } else { foreach ($key in $script:_PSGSuiteSessions.Keys) { Write-Verbose "Clearing cached session with key: $key" $script:_PSGSuiteSessions[$key].Service.Dispose() $toRemove += $key } } } End { foreach ($key in $toRemove) { $script:_PSGSuiteSessions.Remove($key) } } } Export-ModuleMember -Function 'Clear-PSGSuiteServiceCache' function Get-GSToken { <# .SYNOPSIS Requests an Access Token for REST API authentication. Defaults to 3600 seconds token expiration time. .DESCRIPTION Requests an Access Token for REST API authentication. Defaults to 3600 seconds token expiration time. .PARAMETER Scopes The list of scopes to request the token for .PARAMETER AdminEmail The email address of the user to request the token for. This is typically the Admin user. .EXAMPLE $Token = Get-GSToken -Scopes 'https://www.google.com/m8/feeds' -AdminEmail $User $headers = @{ Authorization = "Bearer $($Token)" 'GData-Version' = '3.0' } .LINK https://psgsuite.io/Function%20Help/Authentication/Get-GSToken/ #> Param ( [parameter(Mandatory = $true)] [Alias('Scope')] [ValidateNotNullOrEmpty()] [string[]] $Scopes, [parameter(Mandatory = $false)] [Alias('User')] [ValidateNotNullOrEmpty()] [String] $AdminEmail = $Script:PSGSuite.AdminEmail ) try { Write-Verbose "Acquiring access token" $serviceParams = @{ Scope = $Scopes ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $AdminEmail } $service = New-GoogleService @serviceParams ($service.HttpClientInitializer.GetAccessTokenForRequestAsync()).Result } catch { Write-Verbose "Failed to acquire access token!" $PSCmdlet.ThrowTerminatingError($_) } } Export-ModuleMember -Function 'Get-GSToken' function Get-PSGSuiteServiceCache { <# .SYNOPSIS Returns the dictionary of cached service objects created with New-GoogleService for inspection. .DESCRIPTION Returns the dictionary of cached service objects created with New-GoogleService for inspection. The keys in the session cache dictionary are comprised of the following values which are added to the cache whenever a new session is created: $SessionKey = @($User,$ServiceType,$(($Scope | Sort-Object) -join ";")) -join ";" .PARAMETER IncludeKeys If $true, returns the full service cache dictionary including keys. Defaults to $false. .EXAMPLE Get-PSGSuiteServiceCache .LINK https://psgsuite.io/Function%20Help/Authentication/Get-PSGSuiteServiceCache/ #> [CmdletBinding()] Param ( [parameter(Mandatory = $false,Position = 0)] [Switch] $IncludeKeys ) Begin{ if (-not $script:_PSGSuiteSessions) { $script:_PSGSuiteSessions = @{} } } Process { Write-Verbose "Getting cached session list" if ($IncludeKeys) { $script:_PSGSuiteSessions } else { $script:_PSGSuiteSessions.Values } } } Export-ModuleMember -Function 'Get-PSGSuiteServiceCache' function New-GoogleService { <# .SYNOPSIS Creates a new Google Service object that handles authentication for the scopes specified .DESCRIPTION Creates a new Google Service object that handles authentication for the scopes specified .PARAMETER Scope The scope or scopes to build the service with, e.g. https://www.googleapis.com/auth/admin.reports.audit.readonly .PARAMETER ServiceType The type of service to create, e.g. Google.Apis.Admin.Reports.reports_v1.ReportsService .PARAMETER User The user to request the service for during the authentication process .EXAMPLE $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.reports.audit.readonly' ServiceType = 'Google.Apis.Admin.Reports.reports_v1.ReportsService' } $service = New-GoogleService @serviceParams .LINK https://psgsuite.io/Function%20Help/Authentication/New-GoogleService/ #> [CmdletBinding()] Param( [Parameter(Mandatory = $true,Position = 0)] [ValidateNotNullOrEmpty()] [String[]] $Scope, [Parameter(Mandatory = $true,Position = 1)] [String] $ServiceType, [Parameter(Mandatory = $false,Position = 2)] [Alias('AdminEmail')] [String] $User = $script:PSGSuite.AdminEmail ) Begin { if (-not $script:_PSGSuiteSessions) { $script:_PSGSuiteSessions = @{} } $sessionKey = @($User,$ServiceType,$(($Scope | Sort-Object) -join ";")) -join ";" } Process { if ($script:_PSGSuiteSessions.ContainsKey($sessionKey)) { if (-not $script:_PSGSuiteSessions[$sessionKey].Acknowledged) { Write-Verbose "Using matching cached service for user '$User'" $script:_PSGSuiteSessions[$sessionKey].Acknowledged = $true } $script:_PSGSuiteSessions[$sessionKey].LastUsed = Get-Date $script:_PSGSuiteSessions[$sessionKey] | Select-Object -ExpandProperty Service } else { if ($script:PSGSuite.JSONServiceAccountKey -or $script:PSGSuite.JSONServiceAccountKeyPath) { Write-Verbose "Building ServiceAccountCredential from JSONServiceAccountKey as user '$User'" try { if (-not $script:PSGSuite.JSONServiceAccountKey) { $script:PSGSuite.JSONServiceAccountKey = ([System.IO.File]::ReadAllBytes($script:PSGSuite.JSONServiceAccountKeyPath)) Set-PSGSuiteConfig -ConfigName $script:PSGSuite.ConfigName -JSONServiceAccountKey $script:PSGSuite.JSONServiceAccountKey -Verbose:$false } $stream = New-Object System.IO.MemoryStream $([System.Text.Encoding]::ASCII.GetBytes($script:PSGSuite.JSONServiceAccountKey)), $null $credential = ([Google.Apis.Auth.OAuth2.GoogleCredential]::FromStream($stream)).CreateWithUser($User).CreateScoped($Scope).UnderlyingCredential } catch { $PSCmdlet.ThrowTerminatingError($_) } finally { if ($stream) { $stream.Close() } } } elseif ($script:PSGSuite.P12KeyPath -or $script:PSGSuite.P12Key -or $script:PSGSuite.P12KeyObject) { try { Write-Verbose "Building ServiceAccountCredential from P12Key as user '$User'" if ($script:PSGSuite.P12KeyPath -or $script:PSGSuite.P12Key) { if (-not $script:PSGSuite.P12Key) { $script:PSGSuite.P12Key = ([System.IO.File]::ReadAllBytes($script:PSGSuite.P12KeyPath)) Set-PSGSuiteConfig -ConfigName $script:PSGSuite.ConfigName -P12Key $script:PSGSuite.P12Key -Verbose:$false } if ($script:PSGSuite.P12KeyPassword) { $P12KeyPassword = $script:PSGSuite.P12KeyPassword } else { $P12KeyPassword = "notasecret" } $certificate = New-Object 'System.Security.Cryptography.X509Certificates.X509Certificate2' -ArgumentList ([System.Byte[]]$script:PSGSuite.P12Key),$P12KeyPassword,([System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) } else { $certificate = $script:PSGSuite.P12KeyObject } $credential = New-Object 'Google.Apis.Auth.OAuth2.ServiceAccountCredential' (New-Object 'Google.Apis.Auth.OAuth2.ServiceAccountCredential+Initializer' $script:PSGSuite.AppEmail -Property @{ User = $User Scopes = [string[]]$Scope } ).FromCertificate($certificate) } catch { $PSCmdlet.ThrowTerminatingError($_) } } elseif ($script:PSGSuite.ClientSecretsPath -or $script:PSGSuite.ClientSecrets) { try { $ClientSecretsScopes = @( 'https://www.google.com/m8/feeds' 'https://mail.google.com' 'https://www.googleapis.com/auth/gmail.settings.basic' 'https://www.googleapis.com/auth/gmail.settings.sharing' 'https://www.googleapis.com/auth/calendar' 'https://www.googleapis.com/auth/drive' 'https://www.googleapis.com/auth/tasks' 'https://www.googleapis.com/auth/tasks.readonly' ) if (-not $script:PSGSuite.ClientSecrets) { $script:PSGSuite.ClientSecrets = ([System.IO.File]::ReadAllText($script:PSGSuite.ClientSecretsPath)) Set-PSGSuiteConfig -ConfigName $script:PSGSuite.ConfigName -ClientSecrets $script:PSGSuite.ClientSecrets -Verbose:$false } $credPath = Join-Path (Resolve-Path (Join-Path "~" ".scrthq")) "PSGSuite" Write-Verbose "Building UserCredentials from ClientSecrets as user '$User' and prompting for authorization if necessary." $stream = New-Object System.IO.MemoryStream $([System.Text.Encoding]::ASCII.GetBytes(($script:PSGSuite.ClientSecrets))),$null $credential = [Google.Apis.Auth.OAuth2.GoogleWebAuthorizationBroker]::AuthorizeAsync( [Google.Apis.Auth.OAuth2.GoogleClientSecrets]::Load($stream).Secrets, [string[]]$ClientSecretsScopes, $User, [System.Threading.CancellationToken]::None, $(New-Object 'Google.Apis.Util.Store.FileDataStore' -ArgumentList $credPath,$true), $(if ($PSVersionTable.PSVersion.Major -gt 5) { New-Object 'Google.Apis.Auth.OAuth2.PromptCodeReceiver' } else { New-Object 'Google.Apis.Auth.OAuth2.LocalServerCodeReceiver' }) ).Result } catch { $PSCmdlet.ThrowTerminatingError($_) } finally { if ($stream) { $stream.Close() } } } else { $PSCmdlet.ThrowTerminatingError((ThrowTerm "The current config '$($script:PSGSuite.ConfigName)' does not contain a JSONServiceAccountKeyPath, P12KeyPath, or ClientSecretsPath! PSGSuite is unable to build a credential object for the service without a path to a credential file! Please update the configuration to include a path at least one of the three credential types.")) } $svc = New-Object "$ServiceType" (New-Object 'Google.Apis.Services.BaseClientService+Initializer' -Property @{ HttpClientInitializer = $credential ApplicationName = "PSGSuite" } ) $script:_PSGSuiteSessions[$sessionKey] = ([PSCustomObject]@{ User = $User Scope = $Scope Service = $svc Issued = Get-Date LastUsed = Get-Date Acknowledged = $false }) return $svc } } } Export-ModuleMember -Function 'New-GoogleService' function Add-GSCalendarSubscription { <# .SYNOPSIS Adds a calendar to a users calendar list (aka subscribes to the specified calendar) .DESCRIPTION Adds a calendar to a users calendar list (aka subscribes to the specified calendar) .PARAMETER User The primary email or UserID of the user. 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. .PARAMETER CalendarID The calendar ID of the calendar you would like to subscribe the user to .PARAMETER ColorRgbFormat Whether to use the foregroundColor and backgroundColor fields to write the calendar colors (RGB). If this feature is used, the index-based colorId field will be set to the best matching option automatically. .PARAMETER Selected Whether the calendar content shows up in the calendar UI. Optional. The default is False. .PARAMETER Hidden Whether the calendar has been hidden from the list. Optional. The default is False. .PARAMETER Reminders A list of reminders to add to this calendar. This parameter expects a 'Google.Apis.Calendar.v3.Data.EventReminder[]' object type. You can create objects of this type easily by using the function 'Add-GSCalendarEventReminder' .PARAMETER DefaultReminderMethod The method used by this reminder. Defaults to email. Possible values are: * "email" - Reminders are sent via email. * "sms" - Reminders are sent via SMS. These are only available for G Suite customers. Requests to set SMS reminders for other account types are ignored. * "popup" - Reminders are sent via a UI popup. .PARAMETER DefaultReminderMinutes Number of minutes before the start of the event when the reminder should trigger. Defaults to 30 minutes. Valid values are between 0 and 40320 (4 weeks in minutes). .PARAMETER Notifications A list of notifications to add to this calendar. This parameter expects a 'Google.Apis.Calendar.v3.Data.CalendarNotification[]' object type. You can create objects of this type easily by using the function 'Add-GSCalendarNotification' .PARAMETER DefaultNotificationMethod The method used to deliver the notification. Possible values are: * "email" - Reminders are sent via email. * "sms" - Reminders are sent via SMS. This value is read-only and is ignored on inserts and updates. SMS reminders are only available for G Suite customers. .PARAMETER DefaultNotificationType The type of notification. Possible values are: * "eventCreation" - Notification sent when a new event is put on the calendar. * "eventChange" - Notification sent when an event is changed. * "eventCancellation" - Notification sent when an event is cancelled. * "eventResponse" - Notification sent when an event is changed. * "agenda" - An agenda with the events of the day (sent out in the morning). .PARAMETER Color The color of the calendar. .PARAMETER SummaryOverride The summary that the authenticated user has set for this calendar. .EXAMPLE Add-GSCalendarSubscription -User me -CalendarId john.smith@domain.com -Selected -Color Cyan Adds the calendar 'john.smith@domain.com' to the AdminEmail user's calendar list .LINK https://psgsuite.io/Function%20Help/Calendar/Add-GSCalendarSubscription/ .LINK https://developers.google.com/calendar/v3/reference/calendarList/insert #> [OutputType('Google.Apis.Calendar.v3.Data.CalendarListEntry')] [cmdletbinding()] Param ( [parameter(Mandatory,Position = 0,ValueFromPipeline,ValueFromPipelineByPropertyName)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String] $User, [parameter(Mandatory,Position = 1)] [String[]] $CalendarId, [parameter(Mandatory = $false)] [Switch] $ColorRgbFormat, [parameter()] [Switch] $Selected, [parameter()] [Switch] $Hidden, [parameter()] [Google.Apis.Calendar.v3.Data.EventReminder[]] $Reminders, [parameter()] [ValidateSet('email','sms','popup')] [String] $DefaultReminderMethod, [parameter()] [ValidateRange(0,40320)] [Int] $DefaultReminderMinutes = 30, [parameter()] [Google.Apis.Calendar.v3.Data.CalendarNotification[]] $Notifications, [parameter()] [ValidateSet('email','sms')] [String] $DefaultNotificationMethod, [parameter()] [ValidateSet('eventCreation','eventChange','eventCancellation','eventResponse','agenda')] [String] $DefaultNotificationType, [parameter()] [ValidateSet("Periwinkle","Seafoam","Lavender","Coral","Goldenrod","Beige","Cyan","Grey","Blue","Green","Red")] [String] $Color, [parameter()] [String] $SummaryOverride ) Begin { $colorHash = @{ Periwinkle = 1 Seafoam = 2 Lavender = 3 Coral = 4 Goldenrod = 5 Beige = 6 Cyan = 7 Grey = 8 Blue = 9 Green = 10 Red = 11 } } Process { try { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/calendar' ServiceType = 'Google.Apis.Calendar.v3.CalendarService' User = $User } $service = New-GoogleService @serviceParams foreach ($calId in $CalendarID) { $body = New-Object 'Google.Apis.Calendar.v3.Data.CalendarListEntry' -Property @{ Id = $calId Selected = $Selected Hidden = $Hidden } if ($PSBoundParameters.ContainsKey('Reminders')) { $body.DefaultReminders = $Reminders } elseif ($PSBoundParameters.ContainsKey('DefaultReminderMethod')) { $DefaultReminders = New-Object 'Google.Apis.Calendar.v3.Data.EventReminder' -Property @{ Method = $DefaultReminderMethod Minutes = $DefaultReminderMinutes } $body.DefaultReminders = [Google.Apis.Calendar.v3.Data.EventReminder[]]$DefaultReminders } if ($PSBoundParameters.ContainsKey('Notifications')) { $body.NotificationSettings = New-Object 'Google.Apis.Calendar.v3.Data.CalendarListEntry+NotificationSettingsData' -Property @{ Notifications = [Google.Apis.Calendar.v3.Data.CalendarNotification[]]$Notifications } } elseif ($PSBoundParameters.ContainsKey('DefaultNotificationMethod') -or $PSBoundParameters.ContainsKey('DefaultNotificationType')) { $DefaultNotification = New-Object 'Google.Apis.Calendar.v3.Data.CalendarNotification' if ($PSBoundParameters.ContainsKey('DefaultNotificationMethod')) { $DefaultNotification.Method = $DefaultNotificationMethod } if ($PSBoundParameters.ContainsKey('DefaultNotificationType')) { $DefaultNotification.Type = $DefaultNotificationType } $body.NotificationSettings = New-Object 'Google.Apis.Calendar.v3.Data.CalendarListEntry+NotificationSettingsData' -Property @{ Notifications = [Google.Apis.Calendar.v3.Data.CalendarNotification[]]$DefaultNotification } } foreach ($key in $PSBoundParameters.Keys) { switch ($key) { Color { $body.ColorId = $colorHash[$Color] } Default { if ($body.PSObject.Properties.Name -contains $key) { $body.$key = $PSBoundParameters[$key] } } } } Write-Verbose "Subscribing user '$User' to Calendar '$($calId)'" $request = $service.CalendarList.Insert($body) if ($PSBoundParameters.ContainsKey('ColorRgbFormat')) { $request.ColorRgbFormat = $ColorRgbFormat } $request.Execute() } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Add-GSCalendarSubscription' function Get-GSCalendar { <# .SYNOPSIS Returns metadata for a calendar. If no calendar ID is specified, returns the list of calendar subscriptions via Get-GSCalendarSubscription .DESCRIPTION Returns metadata for a calendar. If no calendar ID is specified, returns the list of calendar subscriptions via Get-GSCalendarSubscription .PARAMETER CalendarId The Id of the calendar you would like to get. If excluded, returns the list of calendars for the user. .PARAMETER User The primary email or UserID of the user. 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. Defaults to the AdminEmail in the config .PARAMETER MinAccessRole The minimum access role for the user in the returned entries. Optional. The default is no restriction. .PARAMETER PageSize Maximum number of entries returned on one result page. The page size can never be larger than 250 entries. .PARAMETER Limit The maximum amount of results you want returned. Exclude or set to 0 to return all results .PARAMETER ShowDeleted Whether to include deleted calendar list entries in the result. Optional. The default is False. .PARAMETER ShowHidden Whether to show hidden entries. Optional. The default is False. .PARAMETER SyncToken Token obtained from the nextSyncToken field returned on the last page of results from the previous list request. It makes the result of this list request contain only entries that have changed since then. If only read-only fields such as calendar properties or ACLs have changed, the entry won't be returned. All entries deleted and hidden since the previous list request will always be in the result set and it is not allowed to set showDeleted neither showHidden to False. To ensure client state consistency minAccessRole query parameter cannot be specified together with nextSyncToken. If the syncToken expires, the server will respond with a 410 GONE response code and the client should clear its storage and perform a full synchronization without any syncToken. Learn more about incremental synchronization. Optional. The default is to return all entries. .EXAMPLE Get-GSCalendar Gets the list of calendar subscriptions for the AdminEmail user. .LINK https://psgsuite.io/Function%20Help/Calendar/Get-GSCalendar/ .LINK https://developers.google.com/calendar/v3/reference/calendars/get .LINK https://developers.google.com/calendar/v3/reference/calendarList/list #> [OutputType('Google.Apis.Calendar.v3.Data.Calendar')] [cmdletbinding(DefaultParameterSetName = "List")] Param ( [parameter(Mandatory,Position = 0,ValueFromPipelineByPropertyName,ParameterSetName = "Get")] [Alias('Id')] [String[]] $CalendarId, [parameter(ValueFromPipelineByPropertyName)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String[]] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false,ParameterSetName = "List")] [Google.Apis.Calendar.v3.CalendarListResource+ListRequest+MinAccessRoleEnum] $MinAccessRole, [parameter(Mandatory = $false,ParameterSetName = "List")] [Alias('MaxResults')] [ValidateRange(1,250)] [Int] $PageSize = 250, [parameter(Mandatory = $false,ParameterSetName = "List")] [Alias('First')] [Int] $Limit = 0, [parameter(Mandatory = $false,ParameterSetName = "List")] [switch] $ShowDeleted, [parameter(Mandatory = $false,ParameterSetName = "List")] [switch] $ShowHidden, [parameter(Mandatory = $false,ParameterSetName = "List")] [String] $SyncToken ) Process { switch ($PSCmdlet.ParameterSetName) { Get { foreach ($U in $User) { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/calendar' ServiceType = 'Google.Apis.Calendar.v3.CalendarService' User = $U } $service = New-GoogleService @serviceParams foreach ($calId in $CalendarId) { try { $request = $service.Calendars.Get($calId) Write-Verbose "Getting Calendar Id '$calId' for User '$U'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } List { Get-GSCalendarSubscription @PSBoundParameters } } } } Export-ModuleMember -Function 'Get-GSCalendar' function Get-GSCalendarAcl { <# .SYNOPSIS Gets the Access Control List for a calendar .DESCRIPTION Gets the Access Control List for a calendar .PARAMETER User The primary email or UserID of the user. 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. Defaults to the AdminEmail in the config .PARAMETER CalendarId The calendar ID of the calendar you would like to list ACLS for. Defaults to the user's primary calendar .PARAMETER RuleId The Id of the Rule you would like to retrieve specifically. Leave empty to return the full ACL list instead. .PARAMETER PageSize Maximum number of events returned on one result page. .PARAMETER Limit The maximum amount of results you want returned. Exclude or set to 0 to return all results .EXAMPLE Get-GSCalendarACL -User me -CalendarID "primary" This gets the ACL on the primary calendar of the AdminUser. .LINK https://psgsuite.io/Function%20Help/Calendar/Get-GSCalendarACL/ .LINK https://developers.google.com/calendar/v3/reference/acl/get .LINK https://developers.google.com/calendar/v3/reference/acl/list #> [OutputType('Google.Apis.Calendar.v3.Data.AclRule')] [cmdletbinding(DefaultParameterSetName = 'List')] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String[]] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias('Id')] [String[]] $CalendarId = "primary", [parameter(Mandatory = $false, ParameterSetName = 'Get')] [String] $RuleId, [parameter(Mandatory = $false, ParameterSetName = 'List')] [ValidateRange(1,2500)] [Int] $PageSize = 2500, [parameter(Mandatory = $false, ParameterSetName = 'List')] [Alias('First')] [Int] $Limit = 0 ) Process { foreach ($U in $User) { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/calendar' ServiceType = 'Google.Apis.Calendar.v3.CalendarService' User = $U } $service = New-GoogleService @serviceParams foreach ($calId in $CalendarId) { try { switch ($PSCmdlet.ParameterSetName) { Get { Write-Verbose "Getting ACL Id '$RuleId' of calendar '$calId' for user '$U'" $request = $service.Acl.Get($calId,$RuleId) $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru | Add-Member -MemberType NoteProperty -Name 'CalendarId' -Value $calId -PassThru } List { $request = $service.Acl.List($calId) foreach ($key in $PSBoundParameters.Keys | Where-Object {$_ -ne 'CalendarId'}) { switch ($key) { Default { if ($request.PSObject.Properties.Name -contains $key) { $request.$key = $PSBoundParameters[$key] } } } } 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) $PageSize = $Limit } $request.MaxResults = $PageSize Write-Verbose "Getting ACL List of calendar '$calId' for user '$U'" [int]$i = 1 $overLimit = $false do { $result = $request.Execute() $result.Items | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru | Add-Member -MemberType NoteProperty -Name 'CalendarId' -Value $calId -PassThru if ($result.NextPageToken) { $request.PageToken = $result.NextPageToken } [int]$retrieved = ($i + $result.Items.Count) - 1 Write-Verbose "Retrieved $retrieved Calendar ACLs..." if ($Limit -gt 0 -and $retrieved -eq $Limit) { Write-Verbose "Limit reached: $Limit" $overLimit = $true } elseif ($Limit -gt 0 -and ($retrieved + $PageSize) -gt $Limit) { $newPS = $Limit - $retrieved Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize,$newPS) $request.MaxResults = $newPS } [int]$i = $i + $result.Items.Count } until ($overLimit -or !$result.NextPageToken) } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } } Export-ModuleMember -Function 'Get-GSCalendarACL' function Get-GSCalendarEvent { <# .SYNOPSIS Gets the calendar events for a user .DESCRIPTION Gets the calendar events for a user .PARAMETER EventId The Id of the event to get info for .PARAMETER CalendarId The calendar ID of the calendar you would like to list events from. Defaults to the user's primary calendar .PARAMETER User The primary email or UserID of the user. 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. Defaults to the AdminEmail in the config .PARAMETER Filter Free text search terms to find events that match these terms in any field, except for extended properties. .PARAMETER OrderBy The order of the events returned in the result. Acceptable values are: * "startTime": Order by the start date/time (ascending). This is only available when querying single events (i.e. the parameter singleEvents is True) * "updated": Order by last modification time (ascending). .PARAMETER MaxAttendees The maximum number of attendees to include in the response. If there are more than the specified number of attendees, only the participant is returned. .PARAMETER PageSize Maximum number of events returned on one result page. .PARAMETER Limit The maximum amount of results you want returned. Exclude or set to 0 to return all results .PARAMETER ShowDeleted Whether to include deleted events (with status equals "cancelled") in the result. Cancelled instances of recurring events (but not the underlying recurring event) will still be included if showDeleted and singleEvents are both False. If showDeleted and singleEvents are both True, only single instances of deleted events (but not the underlying recurring events) are returned. .PARAMETER ShowHiddenInvitations Whether to include hidden invitations in the result. .PARAMETER SingleEvents Whether to expand recurring events into instances and only return single one-off events and instances of recurring events, but not the underlying recurring events themselves. .PARAMETER PrivateExtendedProperty Extended properties constraint specified as a hashtable where propertyName=value. Matches only private properties. .PARAMETER SharedExtendedProperty Extended properties constraint specified as a hashtable where propertyName=value. Matches only shared properties. .PARAMETER TimeMin Lower bound (inclusive) for an event's end time to filter by. If TimeMax is set, TimeMin must be smaller than timeMax. .PARAMETER TimeMax Upper bound (exclusive) for an event's start time to filter by. If TimeMin is set, TimeMax must be greater than timeMin. .EXAMPLE Get-GSCalendarEventList -TimeMin (Get-Date "01-21-2018 00:00:00") -TimeMax (Get-Date "01-28-2018 23:59:59") -SingleEvents This gets the single events on the primary calendar of the Admin for the week of Jan 21-28, 2018. .LINK https://psgsuite.io/Function%20Help/Calendar/Get-GSCalendarEvent/ .LINK https://developers.google.com/calendar/v3/reference/events/get .LINK https://developers.google.com/calendar/v3/reference/events/list #> [OutputType('Google.Apis.Calendar.v3.Data.Event')] [cmdletbinding(DefaultParameterSetName = "List")] Param ( [parameter(Mandatory = $true,Position = 0,ParameterSetName = "Get")] [String[]] $EventId, [parameter(Mandatory = $false)] [String] $CalendarId = "primary", [parameter(Mandatory = $false,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String[]] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false,ParameterSetName = "List")] [Alias('Q','Query')] [String] $Filter, [parameter(Mandatory = $false,ParameterSetName = "List")] [ValidateSet("StartTime","Updated")] [String] $OrderBy, [parameter(Mandatory = $false)] [Int] $MaxAttendees, [parameter(Mandatory = $false,ParameterSetName = "List")] [ValidateRange(1,2500)] [Int] $PageSize = 2500, [parameter(Mandatory = $false,ParameterSetName = 'List')] [Alias('First')] [Int] $Limit = 0, [parameter(Mandatory = $false,ParameterSetName = "List")] [switch] $ShowDeleted, [parameter(Mandatory = $false,ParameterSetName = "List")] [switch] $ShowHiddenInvitations, [parameter(Mandatory = $false,ParameterSetName = "List")] [switch] $SingleEvents, [parameter(Mandatory = $false,ParameterSetName = "List")] [Hashtable] $PrivateExtendedProperty, [parameter(Mandatory = $false,ParameterSetName = "List")] [Hashtable] $SharedExtendedProperty, [parameter(Mandatory = $false,ParameterSetName = "List")] [DateTime] $TimeMin, [parameter(Mandatory = $false,ParameterSetName = "List")] [DateTime] $TimeMax ) Process { foreach ($U in $User) { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/calendar' ServiceType = 'Google.Apis.Calendar.v3.CalendarService' User = $U } $service = New-GoogleService @serviceParams foreach ($calId in $CalendarId) { switch ($PSCmdlet.ParameterSetName) { Get { foreach ($evId in $EventId) { try { $request = $service.Events.Get($calId,$evId) if ($PSBoundParameters.Keys -contains 'MaxAttendees') { $request.MaxAttendees = $MaxAttendees } Write-Verbose "Getting Event ID '$evId' from Calendar '$calId' for User '$U'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru | Add-Member -MemberType NoteProperty -Name 'CalendarId' -Value $calId -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } List { try { $request = $service.Events.List($calId) foreach ($key in $PSBoundParameters.Keys | Where-Object {$_ -ne 'CalendarId'}) { switch ($key) { Filter { $request.Q = $Filter } PrivateExtendedProperty { $converted = $PrivateExtendedProperty.Keys | Foreach-Object { "$($_)=$($PrivateExtendedProperty[$_])" } $repeatable = [Google.Apis.Util.Repeatable[String]]::new([String[]]$converted) $request.PrivateExtendedProperty = $repeatable } SharedExtendedProperty { $converted = $SharedExtendedProperty.Keys | Foreach-Object { "$($_)=$($SharedExtendedProperty[$_])" } $repeatable = [Google.Apis.Util.Repeatable[String]]::new([String[]]$converted) $request.SharedExtendedProperty = $repeatable } Default { if ($request.PSObject.Properties.Name -contains $key) { $request.$key = $PSBoundParameters[$key] } } } } 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) $PageSize = $Limit } $request.MaxResults = $PageSize if ($Filter) { Write-Verbose "Getting all Calendar Events matching filter '$Filter' on calendar '$calId' for user '$U'" } else { Write-Verbose "Getting all Calendar Events on calendar '$calId' for user '$U'" } [int]$i = 1 $overLimit = $false do { $result = $request.Execute() $result.Items | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru | Add-Member -MemberType NoteProperty -Name 'CalendarId' -Value $calId -PassThru if ($result.NextPageToken) { $request.PageToken = $result.NextPageToken } [int]$retrieved = ($i + $result.Items.Count) - 1 Write-Verbose "Retrieved $retrieved Calendar Events..." if ($Limit -gt 0 -and $retrieved -eq $Limit) { Write-Verbose "Limit reached: $Limit" $overLimit = $true } elseif ($Limit -gt 0 -and ($retrieved + $PageSize) -gt $Limit) { $newPS = $Limit - $retrieved Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize,$newPS) $request.MaxResults = $newPS } [int]$i = $i + $result.Items.Count } until ($overLimit -or !$result.NextPageToken) } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } } } } Export-ModuleMember -Function 'Get-GSCalendarEvent' function Get-GSCalendarSubscription { <# .SYNOPSIS Gets a subscribed calendar from a users calendar list. Returns the full calendar list if no CalendarId is specified. .DESCRIPTION Gets a subscribed calendar from a users calendar list. Returns the full calendar list if no CalendarId is specified. .PARAMETER User The primary email or UserID of the user. 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. Defaults to the AdminEmail in the config .PARAMETER CalendarID The calendar ID of the calendar you would like to get info for. If left blank, returns the list of calendars the user is subscribed to. .PARAMETER MinAccessRole The minimum access role for the user in the returned entries. Optional. The default is no restriction. .PARAMETER PageSize Maximum number of entries returned on one result page. The page size can never be larger than 250 entries. .PARAMETER Limit The maximum amount of results you want returned. Exclude or set to 0 to return all results .PARAMETER ShowDeleted Whether to include deleted calendar list entries in the result. Optional. The default is False. .PARAMETER ShowHidden Whether to show hidden entries. Optional. The default is False. .PARAMETER SyncToken Token obtained from the nextSyncToken field returned on the last page of results from the previous list request. It makes the result of this list request contain only entries that have changed since then. If only read-only fields such as calendar properties or ACLs have changed, the entry won't be returned. All entries deleted and hidden since the previous list request will always be in the result set and it is not allowed to set showDeleted neither showHidden to False. To ensure client state consistency minAccessRole query parameter cannot be specified together with nextSyncToken. If the syncToken expires, the server will respond with a 410 GONE response code and the client should clear its storage and perform a full synchronization without any syncToken. Learn more about incremental synchronization. Optional. The default is to return all entries. .EXAMPLE Get-GSCalendarSubscription Gets the AdminEmail user's calendar list .LINK https://psgsuite.io/Function%20Help/Calendar/Get-GSCalendarSubscription/ .LINK https://developers.google.com/calendar/v3/reference/calendarList/get .LINK https://developers.google.com/calendar/v3/reference/calendarList/list #> [OutputType('Google.Apis.Calendar.v3.Data.CalendarListEntry')] [cmdletbinding(DefaultParameterSetName = 'List')] Param ( [parameter(Mandatory,Position = 0,ValueFromPipelineByPropertyName,ParameterSetName = "Get")] [Alias('Id')] [String[]] $CalendarId, [parameter(ValueFromPipelineByPropertyName)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String[]] $User = $Script:PSGSuite.AdminEmail, [parameter(ParameterSetName = "List")] [Google.Apis.Calendar.v3.CalendarListResource+ListRequest+MinAccessRoleEnum] $MinAccessRole, [parameter(ParameterSetName = "List")] [Alias('MaxResults')] [ValidateRange(1,250)] [Int] $PageSize = 250, [parameter(ParameterSetName = "List")] [Alias('First')] [Int] $Limit = 0, [parameter(ParameterSetName = "List")] [switch] $ShowDeleted, [parameter(ParameterSetName = "List")] [switch] $ShowHidden, [parameter(ParameterSetName = "List")] [String] $SyncToken ) Process { foreach ($U in $User) { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/calendar' ServiceType = 'Google.Apis.Calendar.v3.CalendarService' User = $U } $service = New-GoogleService @serviceParams if ($PSCmdlet.ParameterSetName -eq 'Get') { foreach ($calId in $CalendarID) { try { Write-Verbose "Getting subscribed calendar '$($calId)' for user '$U'" $request = $service.CalendarList.Get($calId) $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } else { try { $request = $service.CalendarList.List() foreach ($key in $PSBoundParameters.Keys | Where-Object {$request.PSObject.Properties.Name -contains $_}) { $request.$key = $PSBoundParameters[$key] } 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) $PageSize = $Limit } $request.MaxResults = $PageSize Write-Verbose "Getting CalendarList for user '$U'" [int]$i = 1 $overLimit = $false do { $result = $request.Execute() $result.Items | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru if ($result.NextPageToken) { $request.PageToken = $result.NextPageToken } [int]$retrieved = ($i + $result.Items.Count) - 1 Write-Verbose "Retrieved $retrieved Calendars..." if ($Limit -gt 0 -and $retrieved -eq $Limit) { Write-Verbose "Limit reached: $Limit" $overLimit = $true } elseif ($Limit -gt 0 -and ($retrieved + $PageSize) -gt $Limit) { $newPS = $Limit - $retrieved Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize,$newPS) $request.MaxResults = $newPS } [int]$i = $i + $result.Items.Count } until ($overLimit -or !$result.NextPageToken) } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } } Export-ModuleMember -Function 'Get-GSCalendarSubscription' function New-GSCalendarAcl { <# .SYNOPSIS Adds a new Access Control Rule to a calendar. .DESCRIPTION Adds a new Access Control Rule to a calendar. .PARAMETER User The primary email or UserID of the user. 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. Defaults to the AdminEmail in the config. .PARAMETER CalendarID The Id of the calendar you would like to share Defaults to the user's primary calendar. .PARAMETER Role The role assigned to the scope. Available values are: * "none" - Provides no access. * "freeBusyReader" - Provides read access to free/busy information. * "reader" - Provides read access to the calendar. Private events will appear to users with reader access, but event details will be hidden. * "writer" - Provides read and write access to the calendar. Private events will appear to users with writer access, and event details will be visible. * "owner" - Provides ownership of the calendar. This role has all of the permissions of the writer role with the additional ability to see and manipulate ACLs. .PARAMETER Value The email address of a user or group, or the name of a domain, depending on the scope type. Omitted for type "default". .PARAMETER Type The type of the scope. Available values are: * "default" - The public scope. This is the default value. * "user" - Limits the scope to a single user. * "group" - Limits the scope to a group. * "domain" - Limits the scope to a domain. Note: The permissions granted to the "default", or public, scope apply to any user, authenticated or not. .PARAMETER DisableNotifications When $true, disables sending notifications about the calendar sharing change. .EXAMPLE New-GSCalendarACL -CalendarID jennyappleseed@domain.com -Role reader -Value Jonnyappleseed@domain.com -Type user Gives Jonnyappleseed@domain.com reader access to jennyappleseed's calendar. .LINK https://psgsuite.io/Function%20Help/Calendar/New-GSCalendarACL/ #> [OutputType('Google.Apis.Calendar.v3.Data.AclRule')] [cmdletbinding(DefaultParameterSetName = "AttendeeEmails")] Param ( [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail", "UserKey", "Mail")] [ValidateNotNullOrEmpty()] [String[]] $User = "me", [parameter(Mandatory = $false)] [String[]] $CalendarId = "primary", [parameter(Mandatory = $true)] [ValidateSet("owner", "writer", "reader", "none", "freeBusyReader")] [String] $Role, [parameter(Mandatory = $true)] [String] $Value, [parameter(Mandatory = $false)] [ValidateSet("default", "user", "group", "domain")] [String] $Type = "user", [parameter(Mandatory = $false)] [switch] $DisableNotifications ) Process { foreach ($U in $User) { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/calendar' ServiceType = 'Google.Apis.Calendar.v3.CalendarService' User = $U } $service = New-GoogleService @serviceParams foreach ($calId in $CalendarID) { try { $body = New-Object 'Google.Apis.Calendar.v3.Data.AclRule' $scopeData = New-Object "Google.Apis.Calendar.v3.Data.AclRule+ScopeData" foreach ($key in $PSBoundParameters.Keys) { switch ($key) { Role { $body.Role = $PSBoundParameters[$key] } Type { $scopeData.Type = $PSBoundParameters[$key] } Value { $scopeData.Value = $PSBoundParameters[$key] } Default { if ($body.PSObject.Properties.Name -contains $key) { $body.$key = $PSBoundParameters[$key] } } } } Write-Verbose "Inserting new ACL for type '$Type' with value '$Value' in role '$Role' on calendar '$calId' for user '$U'" $body.Scope = $scopeData $request = $service.Acl.Insert($body, $calId) $request.sendNotifications = -not $DisableNotifications $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } } Export-ModuleMember -Function 'New-GSCalendarACL' function New-GSCalendarEvent { <# .SYNOPSIS Creates a new calendar event .DESCRIPTION Creates a new calendar event .PARAMETER Summary Event summary .PARAMETER Description Event description .PARAMETER Id Opaque identifier of the event. When creating new single or recurring events, you can specify their IDs. Provided IDs must follow these rules: * characters allowed in the ID are those used in base32hex encoding, i.e. lowercase letters a-v and digits 0-9, see section 3.1.2 in RFC2938 * the length of the ID must be between 5 and 1024 characters * the ID must be unique per calendar Due to the globally distributed nature of the system, we cannot guarantee that ID collisions will be detected at event creation time. To minimize the risk of collisions we recommend using an established UUID algorithm such as one described in RFC4122. If you do not specify an ID, it will be automatically generated by the server. .PARAMETER User The primary email or UserID of the user. 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. Defaults to the AdminEmail in the config. .PARAMETER CalendarID The calendar ID of the calendar you would like to list events from. Defaults to the user's primary calendar. .PARAMETER AttendeeEmails The email addresses of the attendees to add. NOTE: This performs simple adds without additional attendee options. If additional options are needed, use the Attendees parameter instead. .PARAMETER Attendees The EventAttendee object(s) to add. Use Add-GSEventAttendee with this parameter for best results. .PARAMETER Location Event location .PARAMETER Visibility Visibility of the event. Possible values are: * "default" - Uses the default visibility for events on the calendar. This is the default value. * "public" - The event is public and event details are visible to all readers of the calendar. * "private" - The event is private and only event attendees may view event details. * "confidential" - The event is private. This value is provided for compatibility reasons. .PARAMETER EventColor Color of the event as seen in Calendar .PARAMETER Reminders A list of reminders to add to this calendar event other than the default calendar reminder. This parameter expects a 'Google.Apis.Calendar.v3.Data.EventReminder[]' object type. You can create objects of this type easily by using the function 'Add-GSCalendarEventReminder' .PARAMETER DisableDefaultReminder When $true, disables inheritance of the default Reminders from the Calendar the event was created on. .PARAMETER LocalStartDateTime Start date and time of the event. Lowest precendence of the three StartDate parameters. Defaults to the time the function is ran. .PARAMETER LocalEndDateTime End date and time of the event. Lowest precendence of the three EndDate parameters. Defaults to 30 minutes after the time the function is ran. .PARAMETER StartDate String representation of the start date. Middle precendence of the three StartDate parameters. .PARAMETER EndDate String representation of the end date. Middle precendence of the three EndDate parameters. .PARAMETER UTCStartDateTime String representation of the start date in UTC. Highest precendence of the three StartDate parameters. .PARAMETER UTCEndDateTime String representation of the end date in UTC. Highest precendence of the three EndDate parameters. .PARAMETER PrivateExtendedProperties A hashtable of properties that are private to the copy of the event that appears on this calendar. .PARAMETER SharedExtendedProperties A hashtable of properties that are shared between copies of the event on other attendees' calendars. .PARAMETER ExtendedProperties Extended properties of the event. This must be of the type 'Google.Apis.Calendar.v3.Data.Event+ExtendedPropertiesData'. This is useful for copying another events ExtendedProperties over when creating a new event. .PARAMETER CreateMeetEvent Create a Google Meet conference event while creating the calendar event. This is useful for creating a Google Meet URL which you can send to people for video conferences. .EXAMPLE New-GSCalendarEvent "Go to the gym" -StartDate (Get-Date "21:00:00") -EndDate (Get-Date "22:00:00") Creates an event titled "Go to the gym" for 9-10PM the day the function is ran. .LINK https://psgsuite.io/Function%20Help/Calendar/New-GSCalendarEvent/ #> [OutputType('Google.Apis.Calendar.v3.Data.Event')] [cmdletbinding(DefaultParameterSetName = "AttendeeEmails")] Param ( [parameter(Mandatory,Position = 0)] [String] $Summary, [parameter()] [String] $Description, [parameter()] [ValidateScript( { if ($_ -match '^[0-9a-v]+$') { $true } else { throw "The characters allowed in the ID are only those used in base32hex encoding, i.e. lowercase letters a-v and digits 0-9" } })] [ValidateLength(5,1024)] [String] $Id, [parameter(ValueFromPipelineByPropertyName)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String[]] $User = $Script:PSGSuite.AdminEmail, [parameter(ValueFromPipelineByPropertyName)] [String[]] $CalendarID = "primary", [parameter(ParameterSetName = "AttendeeEmails")] [String[]] $AttendeeEmails, [parameter(ParameterSetName = "AttendeeObjects")] [Google.Apis.Calendar.v3.Data.EventAttendee[]] $Attendees, [parameter()] [String] $Location, [parameter()] [ValidateSet('default','public','private','confidential')] [String] $Visibility, [parameter()] [ValidateSet("Periwinkle","Seafoam","Lavender","Coral","Goldenrod","Beige","Cyan","Grey","Blue","Green","Red")] [String] $EventColor, [parameter()] [Google.Apis.Calendar.v3.Data.EventReminder[]] $Reminders, [parameter()] [Alias('DisableReminder')] [Switch] $DisableDefaultReminder, [parameter()] [DateTime] $LocalStartDateTime = (Get-Date), [parameter()] [DateTime] $LocalEndDateTime = (Get-Date).AddMinutes(30), [parameter()] [String] $StartDate, [parameter()] [String] $EndDate, [parameter()] [String] $UTCStartDateTime, [parameter()] [String] $UTCEndDateTime, [parameter()] [Hashtable] $PrivateExtendedProperties, [parameter()] [Hashtable] $SharedExtendedProperties, [parameter()] [Google.Apis.Calendar.v3.Data.Event+ExtendedPropertiesData] $ExtendedProperties, [parameter()] [switch] $CreateMeetEvent ) Begin { $colorHash = @{ Periwinkle = 1 Seafoam = 2 Lavender = 3 Coral = 4 Goldenrod = 5 Beige = 6 Cyan = 7 Grey = 8 Blue = 9 Green = 10 Red = 11 } } Process { try { foreach ($U in $User) { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/calendar' ServiceType = 'Google.Apis.Calendar.v3.CalendarService' User = $U } $service = New-GoogleService @serviceParams if ($PSCmdlet.ParameterSetName -eq 'AttendeeEmails' -and $PSBoundParameters.Keys -contains 'AttendeeEmails') { [Google.Apis.Calendar.v3.Data.EventAttendee[]]$Attendees = $AttendeeEmails | ForEach-Object { Add-GSEventAttendee -Email $_ } } $body = New-Object 'Google.Apis.Calendar.v3.Data.Event' if ($Attendees) { $body.Attendees = [Google.Apis.Calendar.v3.Data.EventAttendee[]]$Attendees } $RemindersData = $null foreach ($key in $PSBoundParameters.Keys) { switch ($key) { EventColor { $body.ColorId = $colorHash[$EventColor] } PrivateExtendedProperties { if (-not $ExtendedProperties) { $ExtendedProperties = New-Object 'Google.Apis.Calendar.v3.Data.Event+ExtendedPropertiesData' -Property @{ Private__ = (New-Object 'System.Collections.Generic.Dictionary[string,string]') Shared = (New-Object 'System.Collections.Generic.Dictionary[string,string]') } } elseif (-not $ExtendedProperties.Private__) { $ExtendedProperties.Private__ = (New-Object 'System.Collections.Generic.Dictionary[string,string]') } foreach ($prop in $PrivateExtendedProperties.Keys) { $ExtendedProperties.Private__.Add($prop,$PrivateExtendedProperties[$prop]) } } SharedExtendedProperties { if (-not $ExtendedProperties) { $ExtendedProperties = New-Object 'Google.Apis.Calendar.v3.Data.Event+ExtendedPropertiesData' -Property @{ Private__ = (New-Object 'System.Collections.Generic.Dictionary[string,string]') Shared = (New-Object 'System.Collections.Generic.Dictionary[string,string]') } } elseif (-not $ExtendedProperties.Shared) { $ExtendedProperties.Shared = (New-Object 'System.Collections.Generic.Dictionary[string,string]') } foreach ($prop in $SharedExtendedProperties.Keys) { $ExtendedProperties.Shared.Add($prop,$SharedExtendedProperties[$prop]) } } Reminders { if ($null -eq $RemindersData) { $RemindersData = New-Object 'Google.Apis.Calendar.v3.Data.Event+RemindersData' } $RemindersData.Overrides = $Reminders } DisableDefaultReminder { if ($null -eq $RemindersData) { $RemindersData = New-Object 'Google.Apis.Calendar.v3.Data.Event+RemindersData' } $RemindersData.UseDefault = (-not $DisableDefaultReminder) } Default { if ($body.PSObject.Properties.Name -contains $key) { $body.$key = $PSBoundParameters[$key] } } } } if ($RemindersData) { $body.Reminders = $RemindersData } if ($ExtendedProperties) { $body.ExtendedProperties = $ExtendedProperties } $body.Start = if ($UTCStartDateTime) { New-Object 'Google.Apis.Calendar.v3.Data.EventDateTime' -Property @{ DateTime = $UTCStartDateTime } } elseif ($StartDate) { New-Object 'Google.Apis.Calendar.v3.Data.EventDateTime' -Property @{ Date = (Get-Date $StartDate -Format "yyyy-MM-dd") } } else { New-Object 'Google.Apis.Calendar.v3.Data.EventDateTime' -Property @{ DateTime = $LocalStartDateTime } } $body.End = if ($UTCEndDateTime) { New-Object 'Google.Apis.Calendar.v3.Data.EventDateTime' -Property @{ DateTime = $UTCEndDateTime } } elseif ($EndDate) { New-Object 'Google.Apis.Calendar.v3.Data.EventDateTime' -Property @{ Date = (Get-Date $EndDate -Format "yyyy-MM-dd") } } else { New-Object 'Google.Apis.Calendar.v3.Data.EventDateTime' -Property @{ DateTime = $LocalEndDateTime } } $verbMsg = $null if ($CreateMeetEvent) { $createRequest = New-Object 'Google.Apis.Calendar.v3.Data.CreateConferenceRequest' $createRequest.RequestId = (New-Guid).ToString('n') $confData = New-Object 'Google.Apis.Calendar.v3.Data.ConferenceData' $confData.CreateRequest = $createRequest $body.ConferenceData = $confData $verbMsg = ' with Meet conferencing' } foreach ($calId in $CalendarID) { Write-Verbose "Creating Calendar Event '$($Summary)'$($verbMsg) on calendar '$calId' for user '$U'" $request = $service.Events.Insert($body,$calId) if ($CreateMeetEvent) { $request.ConferenceDataVersion = 1 } $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru | Add-Member -MemberType NoteProperty -Name 'CalendarId' -Value $calId -PassThru } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'New-GSCalendarEvent' function Remove-GSCalendarAcl { <# .SYNOPSIS Removes an Access Control List rule from a calendar. .DESCRIPTION Removes an Access Control List rule from a calendar. .PARAMETER User The primary email or UserID of the user who owns the calendar. 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. .PARAMETER CalendarID The calendar ID of the calendar you would like to remove the ACL from. .PARAMETER RuleId The ACL rule Id to remove. .EXAMPLE Get-GSCalendar -User joe@domain.com | Get-GSCalendarAcl | Where-Object {$_.Role -eq 'Owner'} | Remove-GSCalendarAcl Gets all the calendars for Joe and finds all ACL rules where .LINK https://psgsuite.io/Function%20Help/Calendar/Remove-GSCalendarAcl/ #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "High")] Param ( [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String] $User, [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [String] $CalendarID, [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [Alias('Id')] [String[]] $RuleId ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/calendar' ServiceType = 'Google.Apis.Calendar.v3.CalendarService' User = $User } $service = New-GoogleService @serviceParams foreach ($rule in $RuleId) { try { if ($PSCmdlet.ShouldProcess("Deleting ACL Rule Id '$rule' from Calendar '$CalendarID' for user '$User'")) { Write-Verbose "Deleting ACL Rule Id '$rule' from Calendar '$CalendarID' for user '$User'" $request = $service.Acl.Delete($CalendarID, $rule) $request.Execute() Write-Verbose "ACL Rule Id '$rule' deleted successfully from Calendar '$CalendarID' for user '$User'" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Remove-GSCalendarAcl' function Remove-GSCalendarEvent { <# .SYNOPSIS Removes a calendar event .DESCRIPTION Removes a calendar event .PARAMETER User The primary email or UserID of the user. 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. Defaults to the AdminEmail in the config. .PARAMETER CalendarID The calendar ID of the calendar you would like to remove events from. Defaults to the user's primary calendar. .PARAMETER EventID The EventID to remove .EXAMPLE Remove-GSCalendarEvent -User user@domain.com -EventID _60q30c1g60o30e1i60o4ac1g60rj8gpl88rj2c1h84s34h9g60s30c1g60o30c1g84o3eg9n8gq32d246gq48d1g64o30c1g60o30c1g60o30c1g60o32c1g60o30c1g8csjihhi6oq3igi28h248ghk6ks4agq161144ga46gr4aci488p0 Removes the specified event from user@domain.com's calendar. .LINK https://psgsuite.io/Function%20Help/Calendar/Remove-GSCalendarEvent/ #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "High")] Param ( [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [String] $CalendarID = "primary", [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [Alias('Id')] [String[]] $EventID ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/calendar' ServiceType = 'Google.Apis.Calendar.v3.CalendarService' User = $User } $service = New-GoogleService @serviceParams foreach ($E in $EventId) { try { if ($PSCmdlet.ShouldProcess("Deleting Event Id '$E' from user '$User'")) { Write-Verbose "Deleting Event Id '$E' from user '$User'" $request = $service.Events.Delete($CalendarID, $E) $request.Execute() Write-Verbose "Label Id '$E' deleted successfully from user '$User'" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Remove-GSCalendarEvent' function Remove-GSCalendarSubscription { <# .SYNOPSIS Removes a calendar from a users calendar list (aka unsubscribes from the specified calendar) .DESCRIPTION Removes a calendar from a users calendar list (aka unsubscribes from the specified calendar) .PARAMETER User The primary email or UserID of the user. 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. .PARAMETER CalendarID The calendar ID of the calendar you would like to unsubscribe the user from .EXAMPLE Remove-GSCalendarSubscription -User me -CalendarId john.smith@domain.com Removes the calendar 'john.smith@domain.com' from the AdminEmail user's calendar list .LINK https://psgsuite.io/Function%20Help/Calendar/Remove-GSCalendarSubscription/ #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $true,Position = 0)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String] $User, [parameter(Mandatory = $true,Position = 1)] [String[]] $CalendarId ) Begin { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/calendar' ServiceType = 'Google.Apis.Calendar.v3.CalendarService' User = $User } $service = New-GoogleService @serviceParams } Process { foreach ($calId in $CalendarID) { try { if ($PSCmdlet.ShouldProcess("Unsubscribing user '$User' from Calendar '$($calId)'")) { Write-Verbose "Unsubscribing user '$User' from Calendar '$($calId)'" $request = $service.CalendarList.Delete($calId) $request.Execute() Write-Verbose "User '$User' has been successfully unsubscribed from calendar '$calId'" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Remove-GSCalendarSubscription' function Update-GSCalendarEvent { <# .SYNOPSIS Updates an event .DESCRIPTION Updates an event .PARAMETER EventID The unique Id of the event to update .PARAMETER CalendarID The Id of the calendar Defaults to the user's primary calendar. .PARAMETER User The primary email or UserID of the user. 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. Defaults to the AdminEmail in the config. .PARAMETER Summary Event summary .PARAMETER Description Event description .PARAMETER AttendeeEmails The email addresses of the attendees to add. NOTE: This performs simple adds without additional attendee options. If additional options are needed, use the Attendees parameter instead. .PARAMETER Attendees The EventAttendee object(s) to add. Use Add-GSEventAttendee with this parameter for best results. .PARAMETER Location Event location .PARAMETER Visibility Visibility of the event. Possible values are: * "default" - Uses the default visibility for events on the calendar. This is the default value. * "public" - The event is public and event details are visible to all readers of the calendar. * "private" - The event is private and only event attendees may view event details. * "confidential" - The event is private. This value is provided for compatibility reasons. .PARAMETER EventColor Color of the event as seen in Calendar .PARAMETER Reminders A list of reminders to add to this calendar event. This parameter expects a 'Google.Apis.Calendar.v3.Data.EventReminder[]' object type. You can create objects of this type easily by using the function 'Add-GSCalendarEventReminder' .PARAMETER RemoveAllReminders If $true, removes all reminder overrides and disables the default reminder inheritance from the calendar that the event is on. .PARAMETER DisableDefaultReminder When $true, disables inheritance of the default Reminders from the Calendar the event was created on. .PARAMETER LocalStartDateTime Start date and time of the event. Lowest precendence of the three StartDate parameters. Defaults to the time the function is ran. .PARAMETER LocalEndDateTime End date and time of the event. Lowest precendence of the three EndDate parameters. Defaults to 30 minutes after the time the function is ran. .PARAMETER StartDate String representation of the start date. Middle precendence of the three StartDate parameters. .PARAMETER EndDate String representation of the end date. Middle precendence of the three EndDate parameters. .PARAMETER UTCStartDateTime String representation of the start date in UTC. Highest precendence of the three StartDate parameters. .PARAMETER UTCEndDateTime String representation of the end date in UTC. Highest precendence of the three EndDate parameters. .PARAMETER PrivateExtendedProperties A hashtable of properties that are private to the copy of the event that appears on this calendar. .PARAMETER SharedExtendedProperties A hashtable of properties that are shared between copies of the event on other attendees' calendars. .PARAMETER ExtendedProperties Extended properties of the event. This must be of the type 'Google.Apis.Calendar.v3.Data.Event+ExtendedPropertiesData'. This is useful for copying another events ExtendedProperties over when updating an existing event. .EXAMPLE New-GSCalendarEvent "Go to the gym" -StartDate (Get-Date "21:00:00") -EndDate (Get-Date "22:00:00") Creates an event titled "Go to the gym" for 9-10PM the day the function is ran. #> [OutputType('Google.Apis.Calendar.v3.Data.Event')] [cmdletbinding(DefaultParameterSetName = "AttendeeEmails")] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias('Id')] [String[]] $EventId, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [String[]] $CalendarId = "primary", [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String[]] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false)] [String] $Summary, [parameter(Mandatory = $false)] [String] $Description, [parameter(Mandatory = $false,ParameterSetName = "AttendeeEmails")] [String[]] $AttendeeEmails, [parameter(Mandatory = $false,ParameterSetName = "AttendeeObjects")] [Google.Apis.Calendar.v3.Data.EventAttendee[]] $Attendees, [parameter(Mandatory = $false)] [String] $Location, [parameter(Mandatory = $false)] [ValidateSet('default','public','private','confidential')] [String] $Visibility, [parameter(Mandatory = $false)] [ValidateSet("Periwinkle","Seafoam","Lavender","Coral","Goldenrod","Beige","Cyan","Grey","Blue","Green","Red")] [String] $EventColor, [parameter()] [Google.Apis.Calendar.v3.Data.EventReminder[]] $Reminders, [parameter(Mandatory = $false)] [Switch] $RemoveAllReminders, [parameter(Mandatory = $false)] [Alias('DisableReminder')] [Switch] $DisableDefaultReminder, [parameter(Mandatory = $false)] [DateTime] $LocalStartDateTime, [parameter(Mandatory = $false)] [DateTime] $LocalEndDateTime, [parameter(Mandatory = $false)] [String] $StartDate, [parameter(Mandatory = $false)] [String] $EndDate, [parameter(Mandatory = $false)] [String] $UTCStartDateTime, [parameter(Mandatory = $false)] [String] $UTCEndDateTime, [parameter(Mandatory = $false)] [Hashtable] $PrivateExtendedProperties, [parameter(Mandatory = $false)] [Hashtable] $SharedExtendedProperties, [parameter(Mandatory = $false)] [Google.Apis.Calendar.v3.Data.Event+ExtendedPropertiesData] $ExtendedProperties ) Begin { $colorHash = @{ Periwinkle = 1 Seafoam = 2 Lavender = 3 Coral = 4 Goldenrod = 5 Beige = 6 Cyan = 7 Grey = 8 Blue = 9 Green = 10 Red = 11 } } Process { try { foreach ($U in $User) { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/calendar' ServiceType = 'Google.Apis.Calendar.v3.CalendarService' User = $U } $service = New-GoogleService @serviceParams if ($PSCmdlet.ParameterSetName -eq 'AttendeeEmails' -and $PSBoundParameters.Keys -contains 'AttendeeEmails') { [Google.Apis.Calendar.v3.Data.EventAttendee[]]$Attendees = $AttendeeEmails | ForEach-Object { Add-GSEventAttendee -Email $_ } } $body = New-Object 'Google.Apis.Calendar.v3.Data.Event' if ($Attendees) { $body.Attendees = [Google.Apis.Calendar.v3.Data.EventAttendee[]]$Attendees } $RemindersData = $null foreach ($key in $PSBoundParameters.Keys) { switch ($key) { EventColor { $body.ColorId = $colorHash[$EventColor] } PrivateExtendedProperties { if (-not $ExtendedProperties) { $ExtendedProperties = New-Object 'Google.Apis.Calendar.v3.Data.Event+ExtendedPropertiesData' -Property @{ Private__ = (New-Object 'System.Collections.Generic.Dictionary[string,string]') Shared = (New-Object 'System.Collections.Generic.Dictionary[string,string]') } } elseif (-not $ExtendedProperties.Private__) { $ExtendedProperties.Private__ = (New-Object 'System.Collections.Generic.Dictionary[string,string]') } foreach ($prop in $PrivateExtendedProperties.Keys) { $ExtendedProperties.Private__.Add($prop,$PrivateExtendedProperties[$prop]) } } SharedExtendedProperties { if (-not $ExtendedProperties) { $ExtendedProperties = New-Object 'Google.Apis.Calendar.v3.Data.Event+ExtendedPropertiesData' -Property @{ Private__ = (New-Object 'System.Collections.Generic.Dictionary[string,string]') Shared = (New-Object 'System.Collections.Generic.Dictionary[string,string]') } } elseif (-not $ExtendedProperties.Shared) { $ExtendedProperties.Shared = (New-Object 'System.Collections.Generic.Dictionary[string,string]') } foreach ($prop in $SharedExtendedProperties.Keys) { $ExtendedProperties.Shared.Add($prop,$SharedExtendedProperties[$prop]) } } Reminders { if ($null -eq $RemindersData) { $RemindersData = New-Object 'Google.Apis.Calendar.v3.Data.Event+RemindersData' } $RemindersData.Overrides = $Reminders } RemoveAllReminders { $RemindersData = New-Object 'Google.Apis.Calendar.v3.Data.Event+RemindersData' -Property @{ UseDefault = $false Overrides = New-Object 'System.Collections.Generic.List[Google.Apis.Calendar.v3.Data.EventReminder]' } } DisableDefaultReminder { if ($null -eq $RemindersData) { $RemindersData = New-Object 'Google.Apis.Calendar.v3.Data.Event+RemindersData' } $RemindersData.UseDefault = (-not $DisableDefaultReminder) } Default { if ($body.PSObject.Properties.Name -contains $key) { $body.$key = $PSBoundParameters[$key] } } } } if ($RemindersData) { $body.Reminders = $RemindersData } if ($ExtendedProperties) { $body.ExtendedProperties = $ExtendedProperties } $body.Start = if ($UTCStartDateTime) { New-Object 'Google.Apis.Calendar.v3.Data.EventDateTime' -Property @{ DateTime = $UTCStartDateTime } } elseif ($StartDate) { New-Object 'Google.Apis.Calendar.v3.Data.EventDateTime' -Property @{ Date = (Get-Date $StartDate -Format "yyyy-MM-dd") } } elseif ($LocalStartDateTime) { New-Object 'Google.Apis.Calendar.v3.Data.EventDateTime' -Property @{ DateTime = $LocalStartDateTime } } $body.End = if ($UTCEndDateTime) { New-Object 'Google.Apis.Calendar.v3.Data.EventDateTime' -Property @{ DateTime = $UTCEndDateTime } } elseif ($EndDate) { New-Object 'Google.Apis.Calendar.v3.Data.EventDateTime' -Property @{ Date = (Get-Date $EndDate -Format "yyyy-MM-dd") } } elseif ($LocalEndDateTime) { New-Object 'Google.Apis.Calendar.v3.Data.EventDateTime' -Property @{ DateTime = $LocalEndDateTime } } foreach ($calId in $CalendarID) { foreach ($evId in $EventId) { Write-Verbose "Updating Calendar Event '$evId' on calendar '$calId' for user '$U'" $request = $service.Events.Patch($body,$calId,$evId) $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru | Add-Member -MemberType NoteProperty -Name 'CalendarId' -Value $calId -PassThru | Add-Member -MemberType NoteProperty -Name 'EventId' -Value $evId -PassThru } } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Update-GSCalendarEvent' function Update-GSCalendarSubscription { <# .SYNOPSIS Updates a calendar in a users calendar list (aka subscription) .DESCRIPTION Updates a calendar in a users calendar list (aka subscription) .PARAMETER User The primary email or UserID of the user. 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. .PARAMETER CalendarID The calendar ID of the calendar subscription you would like to update. .PARAMETER ColorRgbFormat Whether to use the foregroundColor and backgroundColor fields to write the calendar colors (RGB). If this feature is used, the index-based colorId field will be set to the best matching option automatically. .PARAMETER Selected Whether the calendar content shows up in the calendar UI. Optional. The default is False. .PARAMETER Hidden Whether the calendar has been hidden from the list. Optional. The default is False. .PARAMETER Reminders A list of reminders to add to this calendar. This parameter expects a 'Google.Apis.Calendar.v3.Data.EventReminder[]' object type. You can create objects of this type easily by using the function 'Add-GSCalendarEventReminder' .PARAMETER RemoveReminders If $true, removes reminders from this CalendarSubscription. .PARAMETER DefaultReminderMethod The method used by this reminder. Defaults to email. Possible values are: * "email" - Reminders are sent via email. * "sms" - Reminders are sent via SMS. These are only available for G Suite customers. Requests to set SMS reminders for other account types are ignored. * "popup" - Reminders are sent via a UI popup. .PARAMETER DefaultReminderMinutes Number of minutes before the start of the event when the reminder should trigger. Defaults to 30 minutes. Valid values are between 0 and 40320 (4 weeks in minutes). .PARAMETER Notifications A list of notifications to add to this calendar. This parameter expects a 'Google.Apis.Calendar.v3.Data.CalendarNotification[]' object type. You can create objects of this type easily by using the function 'Add-GSCalendarNotification' .PARAMETER RemoveNotifications If $true, removes notifications from this CalendarSubscription. .PARAMETER DefaultNotificationMethod The method used to deliver the notification. Possible values are: * "email" - Reminders are sent via email. * "sms" - Reminders are sent via SMS. This value is read-only and is ignored on inserts and updates. SMS reminders are only available for G Suite customers. .PARAMETER DefaultNotificationType The type of notification. Possible values are: * "eventCreation" - Notification sent when a new event is put on the calendar. * "eventChange" - Notification sent when an event is changed. * "eventCancellation" - Notification sent when an event is cancelled. * "eventResponse" - Notification sent when an event is changed. * "agenda" - An agenda with the events of the day (sent out in the morning). .PARAMETER Color The color of the calendar. .PARAMETER SummaryOverride The summary that the authenticated user has set for this calendar. .EXAMPLE Update-GSCalendarSubscription -User me -CalendarId john.smith@domain.com -Selected -Color Cyan Updates the calendar 'john.smith@domain.com' on the AdminEmail user's calendar list by marking it as selected and setting the color to Cyan #> [OutputType('Google.Apis.Calendar.v3.Data.CalendarListEntry')] [cmdletbinding()] Param ( [parameter(Mandatory,Position = 0,ValueFromPipeline,ValueFromPipelineByPropertyName)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String] $User, [parameter(Mandatory,Position = 1,ValueFromPipelineByPropertyName)] [Alias('Id')] [String[]] $CalendarId, [parameter(Mandatory = $false)] [Switch] $ColorRgbFormat, [parameter(Mandatory = $false)] [Switch] $Selected, [parameter(Mandatory = $false)] [Switch] $Hidden, [parameter()] [Google.Apis.Calendar.v3.Data.EventReminder[]] $Reminders, [parameter()] [switch] $RemoveReminders, [parameter(Mandatory = $false)] [ValidateSet('email','sms','popup')] [String] $DefaultReminderMethod, [parameter(Mandatory = $false)] [ValidateRange(0,40320)] [Int] $DefaultReminderMinutes = 10, [parameter()] [Google.Apis.Calendar.v3.Data.CalendarNotification[]] $Notifications, [parameter()] [switch] $RemoveNotifications, [parameter(Mandatory = $false)] [ValidateSet('email','sms')] [String] $DefaultNotificationMethod, [parameter(Mandatory = $false)] [ValidateSet('eventCreation','eventChange','eventCancellation','eventResponse','agenda')] [String] $DefaultNotificationType, [parameter(Mandatory = $false)] [ValidateSet("Periwinkle","Seafoam","Lavender","Coral","Goldenrod","Beige","Cyan","Grey","Blue","Green","Red")] [String] $Color, [parameter(Mandatory = $false)] [String] $SummaryOverride ) Begin { $colorHash = @{ Periwinkle = 1 Seafoam = 2 Lavender = 3 Coral = 4 Goldenrod = 5 Beige = 6 Cyan = 7 Grey = 8 Blue = 9 Green = 10 Red = 11 } } Process { try { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/calendar' ServiceType = 'Google.Apis.Calendar.v3.CalendarService' User = $User } $service = New-GoogleService @serviceParams foreach ($calId in $CalendarID) { $body = Get-GSCalendarSubscription -User $User -CalendarId $calId -Verbose:$false if ($RemoveReminders) { $body.DefaultReminders = New-Object 'System.Collections.Generic.List[Google.Apis.Calendar.v3.Data.EventReminder]' } elseif ($PSBoundParameters.ContainsKey('Reminders')) { if ($null -eq $body.DefaultReminders) { $body.DefaultReminders = $Reminders } else { $Reminders | ForEach-Object { $body.DefaultReminders.Add($_) | Out-Null } } } elseif ($PSBoundParameters.ContainsKey('DefaultReminderMethod')) { $defReminder = New-Object 'Google.Apis.Calendar.v3.Data.EventReminder' -Property @{ Method = $DefaultReminderMethod Minutes = $DefaultReminderMinutes } if ($null -eq $body.DefaultReminders) { $body.DefaultReminders = [Google.Apis.Calendar.v3.Data.EventReminder[]]$defReminder } else { $body.DefaultReminders.Add($defReminder) } } if ($RemoveNotifications) { $body.NotificationSettings = New-Object 'Google.Apis.Calendar.v3.Data.CalendarListEntry+NotificationSettingsData' -Property @{ Notifications = New-Object 'System.Collections.Generic.List[Google.Apis.Calendar.v3.Data.CalendarNotification]' } } elseif ($PSBoundParameters.ContainsKey('Notifications')) { if ($null -eq $body.NotificationSettings) { $body.NotificationSettings = New-Object 'Google.Apis.Calendar.v3.Data.CalendarListEntry+NotificationSettingsData' -Property @{ Notifications = [Google.Apis.Calendar.v3.Data.CalendarNotification[]]$Notifications } } else { $Notifications | ForEach-Object { $body.NotificationSettings.Notifications.Add($_) | Out-Null } } } elseif ($PSBoundParameters.ContainsKey('DefaultNotificationMethod') -and $PSBoundParameters.ContainsKey('DefaultNotificationType')) { if ($null -eq $body.NotificationSettings) { $DefaultNotification = New-Object 'Google.Apis.Calendar.v3.Data.CalendarNotification' -Property @{ Method = $DefaultNotificationMethod Type = $DefaultNotificationType } $body.NotificationSettings = New-Object 'Google.Apis.Calendar.v3.Data.CalendarListEntry+NotificationSettingsData' -Property @{ Notifications = [Google.Apis.Calendar.v3.Data.CalendarNotification[]]$DefaultNotification } } else { $DefaultNotification = New-Object 'Google.Apis.Calendar.v3.Data.CalendarNotification' -Property @{ Method = $DefaultNotificationMethod Type = $DefaultNotificationType } $body.NotificationSettings.Notifications.Add($DefaultNotification) | Out-Null } } foreach ($key in $PSBoundParameters.Keys) { switch ($key) { Color { $body.ColorId = $colorHash[$Color] } Default { if ($body.PSObject.Properties.Name -contains $key) { $body.$key = $PSBoundParameters[$key] } } } } Write-Verbose "Updating Calendar '$($calId) for user '$User'" $request = $service.CalendarList.Patch($body,$calId) if ($PSBoundParameters.ContainsKey('ColorRgbFormat')) { $request.ColorRgbFormat = $ColorRgbFormat } $request.Execute() } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Update-GSCalendarSubscription' function Get-GSChatMember { <# .SYNOPSIS Gets Chat member information .DESCRIPTION Gets Chat member information .PARAMETER Member Resource name of the membership to be retrieved, in the form "spaces/members". Example: spaces/AAAAMpdlehY/members/105115627578887013105 .PARAMETER Space The resource name of the space for which membership list is to be fetched, in the form "spaces". Example: spaces/AAAAMpdlehY .EXAMPLE Get-GSChatMember -Space 'spaces/AAAAMpdlehY' Gets the list of human members in the Chat space specified #> [OutputType('Google.Apis.HangoutsChat.v1.Data.Membership')] [cmdletbinding(DefaultParameterSetName = "List")] Param ( [parameter(Mandatory = $true,ParameterSetName = "Get")] [string[]] $Member, [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = "List")] [Alias('Parent','Name')] [string[]] $Space ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/chat.bot' ServiceType = 'Google.Apis.HangoutsChat.v1.HangoutsChatService' } $service = New-GoogleService @serviceParams } Process { switch ($PSCmdlet.ParameterSetName) { Get { foreach ($mem in $Member) { try { $request = $service.Spaces.Members.Get($mem) Write-Verbose "Getting Member '$mem'" $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } List { foreach ($sp in $Space) { try { if ($sp -notlike "spaces/*") { try { $sp = Get-GSChatConfig -SpaceName $sp -ErrorAction Stop } catch { $sp = "spaces/$sp" } } $request = $service.Spaces.Members.List($sp) Write-Verbose "Getting Member List of Chat Space '$sp'" [int]$i = 1 do { $result = $request.Execute() $result.Memberships if ($result.NextPageToken) { $request.PageToken = $result.NextPageToken } [int]$retrieved = ($i + $result.Memberships.Count) - 1 Write-Verbose "Retrieved $retrieved Memberships..." [int]$i = $i + $result.Memberships.Count } until (!$result.NextPageToken) } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } } } Export-ModuleMember -Function 'Get-GSChatMember' function Get-GSChatMessage { <# .SYNOPSIS Gets a Chat message .DESCRIPTION Gets a Chat message .PARAMETER Name Resource name of the message to be retrieved, in the form "spaces/messages". Example: spaces/AAAAMpdlehY/messages/UMxbHmzDlr4.UMxbHmzDlr4 .EXAMPLE Get-GSChatMessage -Name 'spaces/AAAAMpdlehY/messages/UMxbHmzDlr4.UMxbHmzDlr4' Gets the Chat message specified #> [OutputType('Google.Apis.HangoutsChat.v1.Data.Message')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0)] [Alias('Id')] [string[]] $Name ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/chat.bot' ServiceType = 'Google.Apis.HangoutsChat.v1.HangoutsChatService' } $service = New-GoogleService @serviceParams } Process { foreach ($msg in $Name) { try { $request = $service.Spaces.Messages.Get($msg) Write-Verbose "Getting Message '$msg'" $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Get-GSChatMessage' function Get-GSChatSpace { <# .SYNOPSIS Gets a Chat space .DESCRIPTION Gets a Chat space .PARAMETER Space The resource name of the space for which membership list is to be fetched, in the form "spaces". If left blank, returns the list of spaces the bot is a member of Example: spaces/AAAAMpdlehY .EXAMPLE Get-GSChatSpace Gets the list of Chat spaces the bot is a member of #> [OutputType('Google.Apis.HangoutsChat.v1.Data.Space')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("Name")] [string[]] $Space ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/chat.bot' ServiceType = 'Google.Apis.HangoutsChat.v1.HangoutsChatService' } $service = New-GoogleService @serviceParams } Process { if ($Space) { foreach ($sp in $Space) { try { if ($sp -notlike "spaces/*") { try { $sp = Get-GSChatConfig -SpaceName $sp -ErrorAction Stop } catch { $sp = "spaces/$sp" } } $request = $service.Spaces.Get($sp) Write-Verbose "Getting Chat Space '$sp'" $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } else { try { $spaceArray = @() $request = $service.Spaces.List() Write-Verbose "Getting Chat Space List" [int]$i = 1 do { $result = $request.Execute() $result.Spaces $spaceArray += $result.Spaces if ($result.NextPageToken) { $request.PageToken = $result.NextPageToken } [int]$retrieved = ($i + $result.Spaces.Count) - 1 Write-Verbose "Retrieved $retrieved Spaces..." [int]$i = $i + $result.Spaces.Count } until (!$result.NextPageToken) } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } End { Write-Verbose "Updating PSGSuite Config with Space list" $spaceHashArray = @() $spaceArray | ForEach-Object { if ($_.DisplayName) { $spaceHashArray += @{$_.DisplayName = $_.Name} } else { $member = Get-GSChatMember -Space $_.Name -Verbose:$false $id = $member.Member.Name $primaryEmail = (Get-GSUser -User ($id.Replace('users/',''))).PrimaryEmail $spaceHashArray += @{ $id = $_.Name $member.Member.DisplayName = $_.Name $primaryEmail = $_.Name } } } Set-PSGSuiteConfig -Space $spaceHashArray -Verbose:$false } } Export-ModuleMember -Function 'Get-GSChatSpace' function Remove-GSChatMessage { <# .SYNOPSIS Removes a Chat message .DESCRIPTION Removes a Chat message .PARAMETER Name Resource name of the message to be removed, in the form "spaces/messages". Example: spaces/AAAAMpdlehY/messages/UMxbHmzDlr4.UMxbHmzDlr4 .EXAMPLE Remove-GSChatMessage -Name 'spaces/AAAAMpdlehY/messages/UMxbHmzDlr4.UMxbHmzDlr4' Removes the Chat message specified after confirmation #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $true,Position = 0)] [Alias('Id')] [string[]] $Name ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/chat.bot' ServiceType = 'Google.Apis.HangoutsChat.v1.HangoutsChatService' } $service = New-GoogleService @serviceParams } Process { foreach ($msg in $Name) { try { if ($PSCmdlet.ShouldProcess("Removing Message '$msg'")) { $request = $service.Spaces.Messages.Delete($msg) $request.Execute() Write-Verbose "Successfully removed Message '$msg'" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Remove-GSChatMessage' function Send-GSChatMessage { <# .SYNOPSIS Sends a Chat message .DESCRIPTION Sends a Chat message .PARAMETER Text Plain-text body of the message. .PARAMETER Thread The thread the message belongs to, in the form "spaces/threads". Example: spaces/AAAA3dnRkmI/threads/_EyIp5BthJk .PARAMETER FallbackText A plain-text description of the message's cards, used when the actual cards cannot be displayed (e.g. mobile notifications). .PARAMETER PreviewText Text for generating preview chips. This text will not be displayed to the user, but any links to images, web pages, videos, etc. included here will generate preview chips. .PARAMETER ActionResponseType Part of the ActionResponse. Parameters that a bot can use to configure how its response is posted. The ActionResponseType is the type of bot response. Available values are: * NEW_MESSAGE: Post as a new message in the topic. * UPDATE_MESSAGE: Update the bot's own message. (Only after CARD_CLICKED events.) * REQUEST_CONFIG: Privately ask the user for additional auth or config. .PARAMETER ActionResponseUrl Part of the ActionResponse. Parameters that a bot can use to configure how its response is posted. The ActionResponseUrl is the URL for users to auth or config. (Only for REQUEST_CONFIG response types.) .PARAMETER Parent The resource name of the space to send the message to, in the form "spaces". Example: spaces/AAAAMpdlehY .PARAMETER ThreadKey Opaque thread identifier string that can be specified to group messages into a single thread. If this is the first message with a given thread identifier, a new thread is created. Subsequent messages with the same thread identifier will be posted into the same thread. This relieves bots and webhooks from having to store the Hangouts Chat thread ID of a thread (created earlier by them) to post further updates to it. .PARAMETER RestParent The resource name of the space to send the message to via REST API call, in the form "spaces". Example: spaces/AAAAMpdlehY .PARAMETER Webhook The Url of the Webhook for the space to send the message to. You can safely store an encrypted dictionary of Webhooks in the PSGSuite Config by passing a hashtable to the `-Webhook` parameter, i.e.: Set-PSGSuiteConfig -Webhook @{JobReports = 'https://chat.googleapis.com/v1/spaces/xxxxxxxxxx/messages?key=xxxxxxxxxxxxxxxxxx&token=xxxxxxxxxxxxxxxxxx'} .PARAMETER MessageSegment Any Chat message segment objects created with functions named `Add-GSChat*` passed through the pipeline or added directly to this parameter as values. If section widgets are passed directly to this function, a new section without a SectionHeader will be created and the widgets will be added to it .PARAMETER BodyPassThru If $true, returns the message body. .EXAMPLE Send-GSChatMessage -Text "Post job report:" -Cards $cards -Webhook JobReports Sends a simple Chat message using the JobReports webhook .EXAMPLE Add-GSChatTextParagraph -Text "Guys...","We <b>NEED</b> to <i>stop</i> spending money on <b>crap</b>!" | Add-GSChatKeyValue -TopLabel "Chocolate Budget" -Content '$5.00' -Icon DOLLAR | Add-GSChatKeyValue -TopLabel "Actual Spending" -Content '$5,000,000!' -BottomLabel "WTF" -Icon AIRPLANE | Add-GSChatImage -ImageUrl "https://media.tenor.com/images/f78545a9b520ecf953578b4be220f26d/tenor.gif" -LinkImage | Add-GSChatCardSection -SectionHeader "Dollar bills, y'all" -OutVariable sect1 | Add-GSChatButton -Text "Launch nuke" -OnClick (Add-GSChatOnClick -Url "https://github.com/scrthq/PSGSuite") -Verbose -OutVariable button1 | Add-GSChatButton -Text "Unleash hounds" -OnClick (Add-GSChatOnClick -Url "https://admin.google.com/?hl=en&authuser=0") -Verbose -OutVariable button2 | Add-GSChatCardSection -SectionHeader "What should we do?" -OutVariable sect2 | Add-GSChatCard -HeaderTitle "Makin' moves with" -HeaderSubtitle "DEM GOODIES" -OutVariable card | Add-GSChatTextParagraph -Text "This message sent by <b>PSGSuite</b> via WebHook!" | Add-GSChatCardSection -SectionHeader "Additional Info" -OutVariable sect2 | Send-GSChatMessage -Text "Got that report, boss:" -FallbackText "Mistakes have been made..." -Webhook ReportRoom This example shows the pipeline capabilities of the Chat functions in PSGSuite. Starting from top to bottom: 1. Add a TextParagraph widget 2. Add a KeyValue with an icon 3. Add another KeyValue with a different icon 4. Add an image and create an OnClick event to open the image's URL by using the -LinkImage parameter 5. Add a new section to encapsulate the widgets sent through the pipeline before it 6. Add a TextButton that opens the PSGSuite GitHub repo when clicked 7. Add another TextButton that opens Google Admin Console when clicked 8. Wrap the 2 buttons in a new Section to divide the content 9. Wrap all widgets and sections in the pipeline so far in a Card 10. Add a new TextParagraph as a footer to the message 11. Wrap that TextParagraph in a new section 12. Send the message and include FallbackText that's displayed in the mobile notification. Since the final TextParagraph and Section are not followed by a new Card addition, Send-GSChatMessage will create a new Card just for the remaining segments then send the completed message via Webhook. The Webhook short-name is used to reference the full URL stored in the encrypted Config so it's not displayed in the actual script. .EXAMPLE Get-Service | Select-Object -First 5 | ForEach-Object { Add-GSChatKeyValue -TopLabel $_.DisplayName -Content $_.Status -BottomLabel $_.Name -Icon TICKET } | Add-GSChatCardSection -SectionHeader "Top 5 Services" | Send-GSChatMessage -Text "Service Report:" -FallbackText "Service Report" -Webhook Reports This gets the first 5 Services returned by Get-Service, creates KeyValue widgets for each, wraps it in a section with a header, then sends it to the Reports Webhook #> [OutputType('Google.Apis.HangoutsChat.v1.Data.Message')] [cmdletbinding(DefaultParameterSetName = "Webhook")] Param ( [parameter(Mandatory = $false,Position = 0)] [string[]] $Text, [parameter(Mandatory = $false)] [string] $Thread, [parameter(Mandatory = $false)] [string[]] $FallbackText, [parameter(Mandatory = $false)] [string] $PreviewText, [parameter(Mandatory = $false)] [ValidateSet('NEW_MESSAGE','UPDATE_MESSAGE','REQUEST_CONFIG')] [string] $ActionResponseType, [parameter(Mandatory = $false)] [string] $ActionResponseUrl, [parameter(Mandatory = $true,ParameterSetName = "SDK")] [string[]] $Parent, [parameter(Mandatory = $false,ParameterSetName = "SDK")] [parameter(Mandatory = $false,ParameterSetName = "Rest")] [string] $ThreadKey, [parameter(Mandatory = $true,ParameterSetName = "Rest")] [string[]] $RestParent, [parameter(Mandatory = $true,ParameterSetName = "Webhook")] [string[]] $Webhook, [parameter(Mandatory = $false,ValueFromPipeline = $true)] [Alias('InputObject')] [ValidateScript({ $allowedTypes = "PSGSuite.Chat.Message.Card","PSGSuite.Chat.Message.Card.Section","PSGSuite.Chat.Message.Card.CardAction","PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue" foreach ($item in $_) { if ([string]$($item.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") { $true } else { throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($item.PSTypeNames -join ", ")." } } })] [Object[]] $MessageSegment, [parameter(Mandatory = $true,ParameterSetName = "BodyPassThru")] [switch] $BodyPassThru ) Begin { $addlSections = @() $addlCardActions = @() $addlSectionWidgets = @() switch ($PSCmdlet.ParameterSetName) { default { $body = @{} foreach ($key in $PSBoundParameters.Keys) { switch ($key) { Text { $body['text'] = ($Text -join "`n") } PreviewText { $body['previewText'] = $PSBoundParameters[$key] } FallbackText { $body['fallbackText'] = ($PSBoundParameters[$key] -join "`n") } Thread { $body['thread'] = @{ name = $Thread } } ActionResponseType { if (!$body['actionResponse']) { $body['actionResponse'] = @{} } $body['actionResponse']['type'] = $PSBoundParameters[$key] } ActionResponseUrl { if (!$body['actionResponse']) { $body['actionResponse'] = @{} } $body['actionResponse']['url'] = $PSBoundParameters[$key] } } } } SDK { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/chat.bot' ServiceType = 'Google.Apis.HangoutsChat.v1.HangoutsChatService' } $service = New-GoogleService @serviceParams $body = New-Object 'Google.Apis.HangoutsChat.v1.Data.Message' foreach ($key in $PSBoundParameters.Keys) { switch ($key) { Text { $body.Text = ($PSBoundParameters[$key] -join "`n") } PreviewText { $body.PreviewText = $PSBoundParameters[$key] } FallbackText { $body.FallbackText = ($PSBoundParameters[$key] -join "`n") } Thread { $body.Thread = New-Object 'Google.Apis.HangoutsChat.v1.Data.Thread' -Property @{ Name = $Thread } } ActionResponseType { if (!$body.ActionResponse) { $body.ActionResponse = New-Object 'Google.Apis.HangoutsChat.v1.Data.ActionResponse' } $body.ActionResponse.Type = $PSBoundParameters[$key] } ActionResponseUrl { if (!$body.ActionResponse) { $body.ActionResponse = New-Object 'Google.Apis.HangoutsChat.v1.Data.ActionResponse' } $body.ActionResponse.Url = $PSBoundParameters[$key] } } } } } } Process { foreach ($segment in $MessageSegment) { switch -RegEx ($segment['SDK'].PSTypeNames[0]) { '(.*?)Google\.Apis\.HangoutsChat\.v1\.Data\.Card' { switch ($PSCmdlet.ParameterSetName) { default { if (!$body['cards']) { $body['cards'] = @() } $body['cards'] += $segment['Webhook'] } SDK { if (!$body.Cards) { $body.Cards = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.Card]' } $body.Cards.Add($segment['SDK']) | Out-Null } } } '(.*?)Google\.Apis\.HangoutsChat\.v1\.Data\.Section' { $addlSections += $segment } '(.*?)Google\.Apis\.HangoutsChat\.v1\.Data\.CardAction' { $addlCardActions += $segment } default { Write-Verbose "Matched a $($segment['SDK'].PSTypeNames[0]) in the MessageSegments!" $addlSectionWidgets += $segment } } } } End { switch ($PSCmdlet.ParameterSetName) { default { if ($addlCardActions -or $addlSections -or $addlSectionWidgets) { if (!$body['cards']) { $cardless = $true $body['cards'] = @() } if ($addlSections) { $body['cards'] += ($addlSections | Add-GSChatCard)['Webhook'] $cardless = $false } if ($addlSectionWidgets) { if ($cardless) { $body['cards'] += ($addlSectionWidgets | Add-GSChatCardSection | Add-GSChatCard)['Webhook'] } else { $newSection = ($addlSectionWidgets | Add-GSChatCardSection)['Webhook'] if (!$body['cards'][-1]['sections']) { $body['cards'][-1]['sections'] = @() } $body['cards'][-1]['sections'] += $newSection } $cardless = $false } if ($addlCardActions) { if ($cardless) { $body['cards'] += ($addlCardActions | Add-GSChatCard)['Webhook'] } elseif (!$body['cards'][-1]['cardActions']) { $body['cards'][-1]['cardActions'] = @() } foreach ($cardAction in $addlCardActions) { $body['cards'][-1]['cardActions'] += $cardAction['Webhook'] } } } switch ($PSCmdlet.ParameterSetName) { Webhook { $body = $body | ConvertTo-Json -Depth 15 foreach ($hook in $Webhook) { try { if ($hook -notlike "https://chat.googleapis.com/v1/spaces/*") { $hook = Get-GSChatConfig -WebhookName $hook -ErrorAction Stop } Write-Verbose "Sending Chat Message via Webhook to '$($hook -replace "\?key\=.*",'')'" Invoke-RestMethod -Method Post -Uri ([Uri]$hook) -Body $body -ContentType 'application/json' -Verbose:$false | Add-Member -MemberType NoteProperty -Name 'Webhook' -Value $hook -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Rest { $body = $body | ConvertTo-Json -Depth 15 foreach ($restPar in $RestParent) { try { if ($restPar -notlike "spaces/*") { try { $restPar = Get-GSChatConfig -SpaceName $restPar -ErrorAction Stop } catch { $restPar = "spaces/$restPar" } } $header = @{ Authorization = "Bearer $(Get-GSToken -Scopes "https://www.googleapis.com/auth/chat.bot" -Verbose:$false)" } $hook = "https://chat.googleapis.com/v1/$($restPar)/messages" if ($PSBoundParameters.Keys -contains 'ThreadKey') { $hook = "$($hook)?threadKey=$ThreadKey" $addlText = " in ThreadKey '$ThreadKey'" } else { $addlText = "" } Write-Verbose "Sending Chat Message via REST API to parent '$restPar'$addlText" Invoke-RestMethod -Method Post -Uri ([Uri]$hook) -Headers $header -Body $body -ContentType 'application/json' -Verbose:$false | Add-Member -MemberType NoteProperty -Name 'RestParent' -Value $restPar -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } BodyPassThru { $newBody = @{ token = (Get-GSToken -Scopes "https://www.googleapis.com/auth/chat.bot" -Verbose:$false) body = $body } $newBody = $newBody | ConvertTo-Json -Depth 20 -Compress return $newBody } } } SDK { if ($addlCardActions -or $addlSections -or $addlSectionWidgets) { if (!$body.Cards) { $cardless = $true $body.Cards = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.Card]' } if ($addlSections) { $body.Cards.Add(($addlSections | Add-GSChatCard)['SDK']) | Out-Null $cardless = $false } if ($addlSectionWidgets) { if ($cardless) { $body.Cards.Add(($addlSectionWidgets | Add-GSChatCardSection | Add-GSChatCard)['SDK']) | Out-Null } else { $newSection = ($addlSectionWidgets | Add-GSChatCardSection)['SDK'] if (!$body.Cards[-1].Sections) { $body.Cards[-1].Sections = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.Section]' } $body.Cards[-1].Sections.Add($newSection) | Out-Null } $cardless = $false } if ($addlCardActions) { if ($cardless) { $body.Cards.Add(($addlCardActions | Add-GSChatCard)['SDK']) } elseif (!$body.Cards[-1].CardActions) { $body.Cards[-1].CardActions = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.CardAction]' } foreach ($cardAction in $addlCardActions) { $body.Cards[-1].CardActions.Add($cardAction['SDK']) | Out-Null } } } foreach ($par in $Parent){ try { if ($par -notlike "spaces/*") { try { $par = Get-GSChatConfig -SpaceName $par -ErrorAction Stop } catch { $par = "spaces/$par" } } $request = $service.Spaces.Messages.Create($body,$par) if ($PSBoundParameters.Keys -contains 'ThreadKey') { $request.ThreadKey = $ThreadKey $addlText = " in ThreadKey '$ThreadKey'" } else { $addlText = "" } Write-Verbose "Sending Chat Message via SDK to Space '$par'$addlText" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Parent' -Value $par -PassThru | Add-Member -MemberType NoteProperty -Name 'ThreadKey' -Value $ThreadKey -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } } } Export-ModuleMember -Function 'Send-GSChatMessage' function Update-GSChatMessage { <# .SYNOPSIS Updates a Chat message, i.e. for a CardClicked response .DESCRIPTION Updates a Chat message, i.e. for a CardClicked response .PARAMETER MessageId Resource name, in the form "spaces/messages". Example: spaces/89L51AAAAAE/messages/kbZTbcol8H4.kbZTbcol8H4 .PARAMETER UpdateMask Required. The field paths to be updated. Currently supported field paths: "text", "cards". .PARAMETER Text Plain-text body of the message. .PARAMETER FallbackText A plain-text description of the message's cards, used when the actual cards cannot be displayed (e.g. mobile notifications). .PARAMETER PreviewText Text for generating preview chips. This text will not be displayed to the user, but any links to images, web pages, videos, etc. included here will generate preview chips. .PARAMETER ActionResponseType Part of the ActionResponse. Parameters that a bot can use to configure how its response is posted. The ActionResponseType is the type of bot response. Available values are: * NEW_MESSAGE: Post as a new message in the topic. * UPDATE_MESSAGE: Update the bot's own message. (Only after CARD_CLICKED events.) * REQUEST_CONFIG: Privately ask the user for additional auth or config. .PARAMETER ActionResponseUrl Part of the ActionResponse. Parameters that a bot can use to configure how its response is posted. The ActionResponseUrl is the URL for users to auth or config. (Only for REQUEST_CONFIG response types.) .PARAMETER MessageSegment Any Chat message segment objects created with functions named `Add-GSChat*` passed through the pipeline or added directly to this parameter as values. If section widgets are passed directly to this function, a new section without a SectionHeader will be created and the widgets will be added to it .PARAMETER BodyPassThru If $true, returns the message body. .PARAMETER UseRest If $true, uses the REST API. .EXAMPLE Send-GSChatMessage -Text "Post job report:" -Cards $cards -Webhook (Get-GSChatWebhook JobReports) Sends a simple Chat message using the JobReports webhook .EXAMPLE Add-GSChatTextParagraph -Text "Guys...","We <b>NEED</b> to <i>stop</i> spending money on <b>crap</b>!" | Add-GSChatKeyValue -TopLabel "Chocolate Budget" -Content '$5.00' -Icon DOLLAR | Add-GSChatKeyValue -TopLabel "Actual Spending" -Content '$5,000,000!' -BottomLabel "WTF" -Icon AIRPLANE | Add-GSChatImage -ImageUrl "https://media.tenor.com/images/f78545a9b520ecf953578b4be220f26d/tenor.gif" -LinkImage | Add-GSChatCardSection -SectionHeader "Dollar bills, y'all" -OutVariable sect1 | Add-GSChatButton -Text "Launch nuke" -OnClick (Add-GSChatOnClick -Url "https://github.com/scrthq/PSGSuite") -Verbose -OutVariable button1 | Add-GSChatButton -Text "Unleash hounds" -OnClick (Add-GSChatOnClick -Url "https://admin.google.com/?hl=en&authuser=0") -Verbose -OutVariable button2 | Add-GSChatCardSection -SectionHeader "What should we do?" -OutVariable sect2 | Add-GSChatCard -HeaderTitle "Makin' moves with" -HeaderSubtitle "DEM GOODIES" -OutVariable card | Add-GSChatTextParagraph -Text "This message sent by <b>PSGSuite</b> via WebHook!" | Add-GSChatCardSection -SectionHeader "Additional Info" -OutVariable sect2 | Send-GSChatMessage -Text "Got that report, boss:" -FallbackText "Mistakes have been made..." -Webhook ReportRoom This example shows the pipeline capabilities of the Chat functions in PSGSuite. Starting from top to bottom: 1. Add a TextParagraph widget 2. Add a KeyValue with an icon 3. Add another KeyValue with a different icon 4. Add an image and create an OnClick event to open the image's URL by using the -LinkImage parameter 5. Add a new section to encapsulate the widgets sent through the pipeline before it 6. Add a TextButton that opens the PSGSuite GitHub repo when clicked 7. Add another TextButton that opens Google Admin Console when clicked 8. Wrap the 2 buttons in a new Section to divide the content 9. Wrap all widgets and sections in the pipeline so far in a Card 10. Add a new TextParagraph as a footer to the message 11. Wrap that TextParagraph in a new section 12. Send the message and include FallbackText that's displayed in the mobile notification. Since the final TextParagraph and Section are not followed by a new Card addition, Send-GSChatMessage will create a new Card just for the remaining segments then send the completed message via Webhook. The Webhook short-name is used to reference the full URL stored in the encrypted Config so it's not displayed in the actual script. .EXAMPLE Get-Service | Select-Object -First 5 | ForEach-Object { Add-GSChatKeyValue -TopLabel $_.DisplayName -Content $_.Status -BottomLabel $_.Name -Icon TICKET } | Add-GSChatCardSection -SectionHeader "Top 5 Services" | Send-GSChatMessage -Text "Service Report:" -FallbackText "Service Report" -Webhook Reports This gets the first 5 Services returned by Get-Service, creates KeyValue widgets for each, wraps it in a section with a header, then sends it to the Reports Webhook #> [OutputType('Google.Apis.HangoutsChat.v1.Data.Message')] [cmdletbinding(DefaultParameterSetName = "Update")] Param ( [parameter(Mandatory = $true,Position = 0,ParameterSetName = "Update")] [string] $MessageId, [parameter(Mandatory = $true,ParameterSetName = "BodyPassThru")] [switch] $BodyPassThru, [parameter(Mandatory = $false,Position = 1)] [ValidateSet("text","cards")] [string[]] $UpdateMask, [parameter(Mandatory = $false)] [string[]] $Text, [parameter(Mandatory = $false)] [string[]] $FallbackText, [parameter(Mandatory = $false)] [string] $PreviewText, [parameter(Mandatory = $false)] [ValidateSet('NEW_MESSAGE','UPDATE_MESSAGE','REQUEST_CONFIG')] [string] $ActionResponseType = 'UPDATE_MESSAGE', [parameter(Mandatory = $false)] [string] $ActionResponseUrl, [parameter(Mandatory = $false,ParameterSetName = "Update")] [switch] $UseRest, [parameter(Mandatory = $false,ValueFromPipeline = $true)] [Alias('InputObject')] [ValidateScript({ $allowedTypes = "PSGSuite.Chat.Message.Card","PSGSuite.Chat.Message.Card.Section","PSGSuite.Chat.Message.Card.CardAction","PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue" foreach ($item in $_) { if ([string]$($item.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") { $true } else { throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($item.PSTypeNames -join ", ")." } } })] [Object[]] $MessageSegment ) Begin { $addlSections = @() $addlCardActions = @() $addlSectionWidgets = @() if ($UseRest -or $BodyPassThru) { $body = @{ actionResponse = @{ type = $ActionResponseType } } foreach ($key in $PSBoundParameters.Keys) { switch ($key) { Text { if ($UpdateMask -notcontains 'text') { $UpdateMask += 'text' } $body['text'] = ($Text -join "`n") } PreviewText { $body['previewText'] = $PSBoundParameters[$key] } FallbackText { $body['fallbackText'] = ($PSBoundParameters[$key] -join "`n") } ActionResponseUrl { if (!$body['actionResponse']) { $body['actionResponse'] = @{} } $body['actionResponse']['url'] = $PSBoundParameters[$key] } } } } else { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/chat.bot' ServiceType = 'Google.Apis.HangoutsChat.v1.HangoutsChatService' } $service = New-GoogleService @serviceParams $body = New-Object 'Google.Apis.HangoutsChat.v1.Data.Message' $body.ActionResponse = New-Object 'Google.Apis.HangoutsChat.v1.Data.ActionResponse' $body.ActionResponse.Type = $ActionResponseType foreach ($key in $PSBoundParameters.Keys) { switch ($key) { Text { if ($UpdateMask -notcontains 'text') { $UpdateMask += 'text' } $body.Text = ($PSBoundParameters[$key] -join "`n") } PreviewText { $body.PreviewText = $PSBoundParameters[$key] } FallbackText { $body.FallbackText = ($PSBoundParameters[$key] -join "`n") } ActionResponseUrl { if (!$body.ActionResponse) { $body.ActionResponse = New-Object 'Google.Apis.HangoutsChat.v1.Data.ActionResponse' } $body.ActionResponse.Url = $PSBoundParameters[$key] } } } } } Process { if ($MessageSegment) { if ($UpdateMask -notcontains 'cards') { $UpdateMask += 'cards' } foreach ($segment in $MessageSegment) { switch -RegEx ($segment['SDK'].PSTypeNames[0]) { '(.*?)Google\.Apis\.HangoutsChat\.v1\.Data\.Card' { if ($UseRest -or $BodyPassThru) { if (!$body['cards']) { $body['cards'] = @() } $body['cards'] += $segment['Webhook'] } else { if (!$body.Cards) { $body.Cards = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.Card]' } $body.Cards.Add($segment['SDK']) | Out-Null } } '(.*?)Google\.Apis\.HangoutsChat\.v1\.Data\.Section' { $addlSections += $segment } '(.*?)Google\.Apis\.HangoutsChat\.v1\.Data\.CardAction' { $addlCardActions += $segment } default { Write-Verbose "Matched a $($segment['SDK'].PSTypeNames[0]) in the MessageSegments!" $addlSectionWidgets += $segment } } } } } End { if ($UseRest -or $BodyPassThru) { if ($addlCardActions -or $addlSections -or $addlSectionWidgets) { if (!$body['cards']) { $cardless = $true $body['cards'] = @() } if ($addlSections) { $body['cards'] += ($addlSections | Add-GSChatCard)['Webhook'] $cardless = $false } if ($addlSectionWidgets) { if ($cardless) { $body['cards'] += ($addlSectionWidgets | Add-GSChatCardSection | Add-GSChatCard)['Webhook'] } else { $newSection = ($addlSectionWidgets | Add-GSChatCardSection)['Webhook'] if (!$body['cards'][-1]['sections']) { $body['cards'][-1]['sections'] = @() } $body['cards'][-1]['sections'] += $newSection } $cardless = $false } if ($addlCardActions) { if ($cardless) { $body['cards'] += ($addlCardActions | Add-GSChatCard)['Webhook'] } elseif (!$body['cards'][-1]['cardActions']) { $body['cards'][-1]['cardActions'] = @() } foreach ($cardAction in $addlCardActions) { $body['cards'][-1]['cardActions'] += $cardAction['Webhook'] } } } if ($BodyPassThru) { $newBody = @{ token = (Get-GSToken -Scopes "https://www.googleapis.com/auth/chat.bot" -Verbose:$false) body = $body updateMask = ($UpdateMask -join ',') } $newBody = $newBody | ConvertTo-Json -Depth 20 -Compress return $newBody } else { $body = $body | ConvertTo-Json -Depth 20 try { $header = @{ Authorization = "Bearer $(Get-GSToken -P12KeyPath $Script:PSGSuite.P12KeyPath -Scopes "https://www.googleapis.com/auth/chat.bot" -AppEmail $Script:PSGSuite.AppEmail -AdminEmail $Script:PSGSuite.AdminEmail -Verbose:$false)" } $hook = "https://chat.googleapis.com/v1/$($MessageId)?updateMask=$($UpdateMask -join ',')" Write-Verbose "Updating Chat Message via REST API to parent '$MessageId'" Invoke-RestMethod -Method Put -Uri ([Uri]$hook) -Headers $header -Body $body -ContentType 'application/json' -Verbose:$false | Add-Member -MemberType NoteProperty -Name 'MessageId' -Value $MessageId -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } else { if ($addlCardActions -or $addlSections -or $addlSectionWidgets) { if (!$body.Cards) { $cardless = $true $body.Cards = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.Card]' } if ($addlSections) { $body.Cards.Add(($addlSections | Add-GSChatCard)['SDK']) | Out-Null $cardless = $false } if ($addlSectionWidgets) { if ($cardless) { $body.Cards.Add(($addlSectionWidgets | Add-GSChatCardSection | Add-GSChatCard)['SDK']) | Out-Null } else { $newSection = ($addlSectionWidgets | Add-GSChatCardSection)['SDK'] if (!$body.Cards[-1].Sections) { $body.Cards[-1].Sections = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.Section]' } $body.Cards[-1].Sections.Add($newSection) | Out-Null } $cardless = $false } if ($addlCardActions) { if ($cardless) { $body.Cards.Add(($addlCardActions | Add-GSChatCard)['SDK']) } elseif (!$body.Cards[-1].CardActions) { $body.Cards[-1].CardActions = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.CardAction]' } foreach ($cardAction in $addlCardActions) { $body.Cards[-1].CardActions.Add($cardAction['SDK']) | Out-Null } } } try { $request = $service.Spaces.Messages.Update($body,$MessageId) $request.UpdateMask = $UpdateMask Write-Verbose "Updating Chat Message Id '$MessageId'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'MessageId' -Value $MessageId -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Update-GSChatMessage' function Add-GSCourseParticipant { <# .SYNOPSIS Adds students and/or teachers to a course .DESCRIPTION Adds students and/or teachers to a course .PARAMETER CourseId Identifier of the course to add participants to. This identifier can be either the Classroom-assigned identifier or an alias. .PARAMETER Student Identifier of the user. This identifier can be one of the following: * the numeric identifier for the user * the email address of the user * the string literal "me", indicating the requesting user .PARAMETER Teacher Identifier of the user. This identifier can be one of the following: * the numeric identifier for the user * the email address of the user * the string literal "me", indicating the requesting user .PARAMETER User The user to authenticate the request as .EXAMPLE Add-GSCourseParticipant -CourseId 'architecture-101' -Student plato@athens.edu,aristotle@athens.edu -Teacher zeus@athens.edu #> [OutputType('Google.Apis.Classroom.v1.Data.Student')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] [String] $CourseId, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias('PrimaryEmail','Email','Mail')] [String[]] $Student, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [String[]] $Teacher, [parameter(Mandatory = $false)] [String] $User = $Script:PSGSuite.AdminEmail ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/classroom.rosters' ServiceType = 'Google.Apis.Classroom.v1.ClassroomService' User = $User } $service = New-GoogleService @serviceParams foreach ($part in $Student | Where-Object {-not [String]::IsNullOrEmpty($_)}) { try { $body = New-Object 'Google.Apis.Classroom.v1.Data.Student' if ( -not ($part -as [decimal])) { if ($part -ceq 'me') { $part = $Script:PSGSuite.AdminEmail } elseif ($part -notlike "*@*.*") { $part = "$($part)@$($Script:PSGSuite.Domain)" } } $body.UserId = $part Write-Verbose "Adding Student '$part' to Course '$CourseId'" $request = $service.Courses.Students.Create($body,$CourseId) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } foreach ($part in $Teacher | Where-Object {-not [String]::IsNullOrEmpty($_)}) { try { $body = New-Object 'Google.Apis.Classroom.v1.Data.Teacher' try { [decimal]$part | Out-Null } catch { if ($part -ceq 'me') { $part = $Script:PSGSuite.AdminEmail } elseif ($part -notlike "*@*.*") { $part = "$($part)@$($Script:PSGSuite.Domain)" } } $body.UserId = $part Write-Verbose "Adding Teacher '$part' to Course '$CourseId'" $request = $service.Courses.Teachers.Create($body,$CourseId) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Add-GSCourseParticipant' function Confirm-GSCourseInvitation { <# .SYNOPSIS Accepts an invitation, removing it and adding the invited user to the teachers or students (as appropriate) of the specified course. Only the invited user may accept an invitation. .DESCRIPTION Accepts an invitation, removing it and adding the invited user to the teachers or students (as appropriate) of the specified course. Only the invited user may accept an invitation. .PARAMETER Id Identifier of the invitation to accept. .PARAMETER User Email or email name part of the invited user. .EXAMPLE Confirm-GSCourseInvitation -Id $inviteId -User aristotle@athens.edu #> [cmdletbinding()] Param ( [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [String] $Id, [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [String] $User ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/classroom.rosters' ServiceType = 'Google.Apis.Classroom.v1.ClassroomService' User = $User } $service = New-GoogleService @serviceParams try { Write-Verbose "Accepting Invitation '$Id' for user '$User'" $request = $service.Invitations.Accept($Id) $request.Execute() Write-Verbose "The Invitation has been successfully accepted for user '$User'" } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Confirm-GSCourseInvitation' function Get-GSClassroomUserProfile { <# .SYNOPSIS Gets a classroom user profile .DESCRIPTION Gets a classroom user profile .PARAMETER UserId Identifier of the profile to return. The identifier can be one of the following: * the numeric identifier for the user * the email address of the user * the string literal "me", indicating the requesting user .PARAMETER Fields The specific fields to fetch .EXAMPLE Get-GSClassroomUserProfile -UserId aristotle@athens.edu #> [OutputType('Google.Apis.Classroom.v1.Data.UserProfile')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias('Id','PrimaryEmail','Mail','UserKey')] [ValidateNotNullOrEmpty()] [String[]] $UserId, [parameter(Mandatory = $false)] [String[]] $Fields = '*' ) Begin { $serviceParams = @{ Scope = @( 'https://www.googleapis.com/auth/classroom.rosters' 'https://www.googleapis.com/auth/classroom.profile.emails' 'https://www.googleapis.com/auth/classroom.profile.photos' ) ServiceType = 'Google.Apis.Classroom.v1.ClassroomService' } $service = New-GoogleService @serviceParams } Process { foreach ($part in $UserId) { try { if ( -not ($part -as [decimal])) { if ($part -ceq 'me') { $part = $Script:PSGSuite.AdminEmail } elseif ($part -notlike "*@*.*") { $part = "$($part)@$($Script:PSGSuite.Domain)" } } Write-Verbose "Getting Classroom User Profile for '$part'" $request = $service.UserProfiles.Get($part) $request.Fields = "$($Fields -join ",")" $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Get-GSClassroomUserProfile' function Get-GSCourse { <# .SYNOPSIS Gets a classroom course or list of courses .DESCRIPTION Gets a classroom course or list of courses .PARAMETER Id Identifier of the course to return. This identifier can be either the Classroom-assigned identifier or an alias. If excluded, returns the list of courses. .PARAMETER Teacher Restricts returned courses to those having a teacher with the specified identifier. The identifier can be one of the following: * the numeric identifier for the user * the email address of the user * the string literal "me", indicating the requesting user .PARAMETER Student Restricts returned courses to those having a student with the specified identifier. The identifier can be one of the following: * the numeric identifier for the user * the email address of the user * the string literal "me", indicating the requesting user .PARAMETER CourseStates Restricts returned courses to those in one of the specified states. .PARAMETER User The user to authenticate the request as .EXAMPLE Get-GSCourse -Teacher aristotle@athens.edu #> [OutputType('Google.Apis.Classroom.v1.Data.Course')] [cmdletbinding(DefaultParameterSetName = "List")] Param ( [parameter(Mandatory = $false,Position = 0,ParameterSetName = "Get")] [ValidateNotNullOrEmpty()] [String[]] $Id, [parameter(Mandatory = $false,ParameterSetName = "List")] [String] $Teacher, [parameter(Mandatory = $false,ParameterSetName = "List")] [String] $Student, [parameter(Mandatory = $false,ParameterSetName = "List")] [Google.Apis.Classroom.v1.CoursesResource+ListRequest+CourseStatesEnum[]] $CourseStates, [parameter(Mandatory = $false)] [String] $User = $Script:PSGSuite.AdminEmail ) Begin { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/classroom.courses' ServiceType = 'Google.Apis.Classroom.v1.ClassroomService' User = $User } $service = New-GoogleService @serviceParams } Process { if ($PSBoundParameters.Keys -contains 'Id') { foreach ($I in $Id) { try { Write-Verbose "Getting Course '$Id'" $request = $service.Courses.Get($Id) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } else { try { Write-Verbose "Getting Course List" $request = $service.Courses.List() foreach ($s in $CourseStates) { $request.CourseStates += $s } if ($PSBoundParameters.Keys -contains 'Student') { $request.StudentId = $PSBoundParameters['Student'] } if ($PSBoundParameters.Keys -contains 'Teacher') { $request.TeacherId = $PSBoundParameters['Teacher'] } [int]$i = 1 do { $result = $request.Execute() if ($null -ne $result.Courses) { $result.Courses } $request.PageToken = $result.NextPageToken [int]$retrieved = ($i + $result.Courses.Count) - 1 Write-Verbose "Retrieved $retrieved Courses..." [int]$i = $i + $result.Courses.Count } until (!$result.NextPageToken) } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Get-GSCourse' function Get-GSCourseAlias { <# .SYNOPSIS Gets the list of aliases for a course. .DESCRIPTION Gets the list of aliases for a course. .PARAMETER CourseId Identifier of the course to alias. This identifier can be either the Classroom-assigned identifier or an alias. .PARAMETER User The user to authenticate the request as .EXAMPLE Get-GSCourseAlias -CourseId 'architecture-101' #> [OutputType('Google.Apis.Classroom.v1.Data.CourseAlias')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0)] [String] $CourseId, [parameter(Mandatory = $false)] [String] $User = $Script:PSGSuite.AdminEmail ) Begin { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/classroom.courses' ServiceType = 'Google.Apis.Classroom.v1.ClassroomService' User = $User } $service = New-GoogleService @serviceParams } Process { try { Write-Verbose "Getting Alias list for Course '$CourseId'" $request = $service.Courses.Aliases.List($CourseId) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSCourseAlias' function Get-GSCourseInvitation { <# .SYNOPSIS Gets a course invitation or list of invitations .DESCRIPTION Gets a course invitation or list of invitations .PARAMETER Id Identifier of the invitation to return. .PARAMETER CourseId Restricts returned invitations to those for a course with the specified identifier. This identifier can be either the Classroom-assigned identifier or an alias. .PARAMETER UserId Restricts returned invitations to those for a specific user. The identifier can be one of the following: * the numeric identifier for the user * the email address of the user * the string literal "me", indicating the requesting user .PARAMETER User The user to authenticate the request as .EXAMPLE Get-GSCourseInvitation -CourseId philosophy-101 #> [OutputType('Google.Apis.Classroom.v1.Data.Invitation')] [cmdletbinding(DefaultParameterSetName = "List")] Param ( [parameter(Mandatory = $true,ParameterSetName = "Get")] [String[]] $Id, [parameter(Mandatory = $false,ParameterSetName = "List")] [String] $CourseId, [parameter(Mandatory = $false,ParameterSetName = "List")] [String] $UserId, [parameter(Mandatory = $false)] [String] $User = $Script:PSGSuite.AdminEmail ) Begin { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/classroom.rosters' ServiceType = 'Google.Apis.Classroom.v1.ClassroomService' User = $User } $service = New-GoogleService @serviceParams } Process { switch ($PSCmdlet.ParameterSetName) { Get { foreach ($part in $Id) { try { Write-Verbose "Getting Invitation ID '$part'" $request = $service.Invitations.Get($part) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } List { try { if ($PSBoundParameters.Keys -notcontains 'CourseId' -and $PSBoundParameters.Keys -notcontains 'UserId') { Write-Error "You must specify a CourseId and/or a UserId!" } else { $request = $service.Invitations.List() $verbMsg = "" if ($PSBoundParameters.Keys -contains 'CourseId') { $verbMsg += " [Course: $CourseId]" $request.CourseId = $CourseId } if ($PSBoundParameters.Keys -contains 'UserId') { if ( -not ($UserId -as [decimal])) { if ($UserId -ceq 'me') { $UserId = $Script:PSGSuite.AdminEmail } elseif ($UserId -notlike "*@*.*") { $UserId = "$($UserId)@$($Script:PSGSuite.Domain)" } } $verbMsg += " [User: $UserId]" $request.UserId = $UserId } Write-Verbose "Getting List of Invitations for$($verbMsg)" [int]$retrieved = 0 [int]$i = 1 do { $result = $request.Execute() if ($null -ne $result.Invitations) { $result.Invitations } [int]$retrieved = ($i + $result.Invitations.Count) - 1 [int]$i = $i + $result.Invitations.Count $request.PageToken = $result.NextPageToken Write-Verbose "Retrieved $retrieved Invitations..." } until (!$result.NextPageToken) } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } } Export-ModuleMember -Function 'Get-GSCourseInvitation' function Get-GSCourseParticipant { <# .SYNOPSIS Gets a course participant or list of participants (teachers/students) .DESCRIPTION Gets a course participant or list of participants (teachers/students) .PARAMETER CourseId Identifier of the course to get participants of. This identifier can be either the Classroom-assigned identifier or an alias. .PARAMETER Role The Role for which you would like to list participants for. Available values are: * Student * Teacher The default value for this parameter is @('Teacher','Student') .PARAMETER Teacher Restricts returned courses to those having a teacher with the specified identifier. The identifier can be one of the following: * the numeric identifier for the user * the email address of the user * the string literal "me", indicating the requesting user .PARAMETER Student Restricts returned courses to those having a student with the specified identifier. The identifier can be one of the following: * the numeric identifier for the user * the email address of the user * the string literal "me", indicating the requesting user .PARAMETER User The user to authenticate the request as .PARAMETER Fields The specific fields to fetch .EXAMPLE Get-GSCourseParticipant -Teacher aristotle@athens.edu #> [OutputType('Google.Apis.Classroom.v1.Data.Student')] [cmdletbinding(DefaultParameterSetName = "List")] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [alias('Id')] [String] $CourseId, [parameter(Mandatory = $false,ParameterSetName = "List")] [ValidateSet('Teacher','Student')] [String[]] $Role = @('Teacher','Student'), [parameter(Mandatory = $false,ParameterSetName = "Get")] [String[]] $Teacher, [parameter(Mandatory = $false,ParameterSetName = "Get")] [String[]] $Student, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [String] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false)] [String[]] $Fields = '*' ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = @( 'https://www.googleapis.com/auth/classroom.rosters' 'https://www.googleapis.com/auth/classroom.profile.emails' 'https://www.googleapis.com/auth/classroom.profile.photos' ) ServiceType = 'Google.Apis.Classroom.v1.ClassroomService' User = $User } $service = New-GoogleService @serviceParams switch ($PSCmdlet.ParameterSetName) { Get { foreach ($part in $Student) { try { if ( -not ($part -as [decimal])) { if ($part -ceq 'me') { $part = $Script:PSGSuite.AdminEmail } elseif ($part -notlike "*@*.*") { $part = "$($part)@$($Script:PSGSuite.Domain)" } } Write-Verbose "Getting Student '$part' for Course '$CourseId'" $request = $service.Courses.Students.Get($CourseId,$part) $request.Fields = "$($Fields -join ",")" $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } foreach ($part in $Teacher) { try { try { [decimal]$part | Out-Null } catch { if ($part -ceq 'me') { $part = $Script:PSGSuite.AdminEmail } elseif ($part -notlike "*@*.*") { $part = "$($part)@$($Script:PSGSuite.Domain)" } } Write-Verbose "Getting Teacher '$part' for Course '$CourseId'" $request = $service.Courses.Teachers.Get($CourseId,$part) $request.Fields = "$($Fields -join ",")" $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } List { foreach ($Ro in $Role) { try { Write-Verbose "Getting List of $($Ro)s for Course '$CourseId'" $request = switch ($Ro) { Teacher { $service.Courses.Teachers.List($CourseId) } Student { $service.Courses.Students.List($CourseId) } } $request.Fields = "$($Fields -join ",")" [int]$retrieved = 0 [int]$i = 1 do { $result = $request.Execute() switch ($Ro) { Teacher { if ($null -ne $result.Teachers) { $result.Teachers } [int]$retrieved = ($i + $result.Teachers.Count) - 1 [int]$i = $i + $result.Teachers.Count } Student { if ($null -ne $result.Students) { $result.Students } [int]$retrieved = ($i + $result.Students.Count) - 1 [int]$i = $i + $result.Students.Count } } $request.PageToken = $result.NextPageToken Write-Verbose "Retrieved $retrieved $($Ro)s..." } until (!$result.NextPageToken) } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } } } Export-ModuleMember -Function 'Get-GSCourseParticipant' function Get-GSStudentGuardian { <# .SYNOPSIS Gets a guardian or list of guardians for a student. .DESCRIPTION Gets a guardian or list of guardians for a student. .PARAMETER StudentId The identifier of the student to get guardian info for. The identifier can be one of the following: * the numeric identifier for the user * the email address of the user * the string literal "me", indicating the requesting user * the string literal "-", indicating that results should be returned for all students that the requesting user is permitted to view guardians for. [Default] * **This is only allowed when excluding the `GuardianId` parameter to perform a List request!** .PARAMETER GuardianId The id field from a Guardian. .PARAMETER User The user to authenticate the request as .EXAMPLE Get-GSStudentGuardian Gets the list of guardians for all students. #> [OutputType('Google.Apis.Classroom.v1.Data.Guardian')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias('Student')] [String[]] $StudentId = "-", [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias('Guardian')] [String[]] $GuardianId, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [String] $User = $Script:PSGSuite.AdminEmail ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/classroom.guardianlinks.students' ServiceType = 'Google.Apis.Classroom.v1.ClassroomService' User = $User } $service = New-GoogleService @serviceParams foreach ($stuId in $StudentId) { try { if ($stuId -ne '-') { if ( -not ($stuId -as [decimal])) { if ($stuId -ceq 'me') { $stuId = $Script:PSGSuite.AdminEmail } elseif ($stuId -notlike "*@*.*") { $stuId = "$($stuId)@$($Script:PSGSuite.Domain)" } } } elseif ($PSBoundParameters.Keys -contains 'GuardianId') { Write-Error "You must specify a valid StudentId when including a GuardianId! Current value '$stuId'" } if ($PSBoundParameters.Keys -contains 'GuardianId') { foreach ($guard in $GuardianId) { try { Write-Verbose "Getting Guardian '$guard' for Student '$stuId'" $request = $service.UserProfiles.Guardians.Get($stuId,$guard) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } else { try { if ($stuId -eq '-') { Write-Verbose "Listing all Guardians" } else { Write-Verbose "Listing Guardians for Student '$stuId'" } $request = $service.UserProfiles.Guardians.List($stuId) [int]$i = 1 do { $result = $request.Execute() if ($null -ne $result.Guardians) { $result.Guardians } $request.PageToken = $result.NextPageToken [int]$retrieved = ($i + $result.Guardians.Count) - 1 Write-Verbose "Retrieved $retrieved Guardians..." [int]$i = $i + $result.Guardians.Count } until (!$result.NextPageToken) } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Get-GSStudentGuardian' function Get-GSStudentGuardianInvitation { <# .SYNOPSIS Gets a guardian invitation or list of guardian invitations. .DESCRIPTION Gets a guardian invitation or list of guardian invitations. .PARAMETER InvitationId The id field of the GuardianInvitation being requested. .PARAMETER StudentId The identifier of the student whose guardian invitation is being requested. The identifier can be one of the following: * the numeric identifier for the user * the email address of the user * the string literal "me", indicating the requesting user * the string literal "-", indicating that results should be returned for all students that the requesting user is permitted to view guardian invitations. [Default] * **This is only allowed when excluding the `InvitationId` parameter to perform a List request!** .PARAMETER GuardianEmail If specified, only results with the specified GuardianEmail will be returned. .PARAMETER States If specified, only results with the specified state values will be returned. Otherwise, results with a state of PENDING will be returned. The State can be one of the following: * PENDING * COMPLETE .PARAMETER User The user to authenticate the request as .EXAMPLE Get-GSStudentGuardianInvitation -StudentId aristotle@athens.edu Gets the list of guardian invitations for this student. #> [OutputType('Google.Apis.Classroom.v1.Data.GuardianInvitation')] [cmdletbinding(DefaultParameterSetName = "List")] Param ( [parameter(Mandatory = $true,ParameterSetName = "Get")] [Alias('Id')] [String[]] $InvitationId, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias('Student')] [String[]] $StudentId = "-", [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true,ParameterSetName = "List")] [Alias('Guardian')] [String] $GuardianEmail, [parameter(Mandatory = $false,ParameterSetName = "List")] [Google.Apis.Classroom.v1.UserProfilesResource+GuardianInvitationsResource+ListRequest+StatesEnum[]] $States, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [String] $User = $Script:PSGSuite.AdminEmail ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/classroom.guardianlinks.students' ServiceType = 'Google.Apis.Classroom.v1.ClassroomService' User = $User } $service = New-GoogleService @serviceParams foreach ($stuId in $StudentId) { try { if ($stuId -ne '-') { if ( -not ($stuId -as [decimal])) { if ($stuId -ceq 'me') { $stuId = $Script:PSGSuite.AdminEmail } elseif ($stuId -notlike "*@*.*") { $stuId = "$($stuId)@$($Script:PSGSuite.Domain)" } } } elseif ($PSCmdlet.ParameterSetName -eq 'Get') { Write-Error "You must specify a valid StudentId when using InvitationId! Current value '$stuId'" } switch ($PSCmdlet.ParameterSetName) { Get { foreach ($invId in $InvitationId) { try { Write-Verbose "Getting Guardian Invitation '$invId' for Student '$stuId'" $request = $service.UserProfiles.GuardianInvitations.Get($stuId,$invId) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } List { try { if ($stuId -eq '-') { Write-Verbose "Listing all Guardian Invitations" } else { Write-Verbose "Listing Guardian Invitations for Student '$stuId'" } $request = $service.UserProfiles.GuardianInvitations.List($stuId) foreach ($s in $States) { $request.States += $s } if ($PSBoundParameters.Keys -contains 'GuardianEmail') { $request.InvitedEmailAddress = $GuardianEmail } [int]$i = 1 do { $result = $request.Execute() if ($null -ne $result.GuardianInvitations) { $result.GuardianInvitations } $request.PageToken = $result.NextPageToken [int]$retrieved = ($i + $result.GuardianInvitations.Count) - 1 Write-Verbose "Retrieved $retrieved Guardian Invitations..." [int]$i = $i + $result.GuardianInvitations.Count } until (!$result.NextPageToken) } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Get-GSStudentGuardianInvitation' function New-GSCourse { <# .SYNOPSIS Creates a course. .DESCRIPTION Creates a course. .PARAMETER Name Name of the course. For example, "10th Grade Biology". The name is required. It must be between 1 and 750 characters and a valid UTF-8 string. .PARAMETER OwnerId The identifier of the owner of a course. When specified as a parameter of a create course request, this field is required. The identifier can be one of the following: * the numeric identifier for the user * the email address of the user * the string literal "me", indicating the requesting user .PARAMETER Id Identifier for this course assigned by Classroom. When creating a course, you may optionally set this identifier to an alias string in the request to create a corresponding alias. The id is still assigned by Classroom and cannot be updated after the course is created. .PARAMETER Section Section of the course. For example, "Period 2". If set, this field must be a valid UTF-8 string and no longer than 2800 characters. .PARAMETER DescriptionHeading Optional heading for the description. For example, "Welcome to 10th Grade Biology." If set, this field must be a valid UTF-8 string and no longer than 3600 characters. .PARAMETER Description Optional description. For example, "We'll be learning about the structure of living creatures from a combination of textbooks, guest lectures, and lab work. Expect to be excited!" If set, this field must be a valid UTF-8 string and no longer than 30,000 characters. .PARAMETER Room Optional room location. For example, "301". If set, this field must be a valid UTF-8 string and no longer than 650 characters. .PARAMETER CourseState State of the course. If unspecified, the default state is PROVISIONED Available values are: * ACTIVE - The course is active. * ARCHIVED - The course has been archived. You cannot modify it except to change it to a different state. * PROVISIONED - The course has been created, but not yet activated. It is accessible by the primary teacher and domain administrators, who may modify it or change it to the ACTIVE or DECLINED states. A course may only be changed to PROVISIONED if it is in the DECLINED state. * DECLINED - The course has been created, but declined. It is accessible by the course owner and domain administrators, though it will not be displayed in the web UI. You cannot modify the course except to change it to the PROVISIONED state. A course may only be changed to DECLINED if it is in the PROVISIONED state. .PARAMETER User The user to authenticate the request as .EXAMPLE New-GSCourse -Name "The Rebublic" -OwnerId plato@athens.edu -Id the-republic-s01 -Section s01 -DescriptionHeading "The definition of justice, the order and character of the just city-state and the just man" -Room academy-01 #> [OutputType('Google.Apis.Classroom.v1.Data.Course')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0)] [ValidateLength(1,750)] [String] $Name, [parameter(Mandatory = $false)] [Alias('Teacher')] [String] $OwnerId, [parameter(Mandatory = $false)] [Alias('Alias')] [String] $Id, [parameter(Mandatory = $false)] [ValidateLength(1,2800)] [String] $Section, [parameter(Mandatory = $false)] [ValidateLength(1,3600)] [Alias('Heading')] [String] $DescriptionHeading, [parameter(Mandatory = $false)] [ValidateLength(1,30000)] [String] $Description, [parameter(Mandatory = $false)] [String] $Room, [parameter(Mandatory = $false)] [Alias('Status')] [ValidateSet('PROVISIONED','ACTIVE','ARCHIVED','DECLINED')] [String] $CourseState, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [String] $User = $Script:PSGSuite.AdminEmail ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/classroom.courses' ServiceType = 'Google.Apis.Classroom.v1.ClassroomService' User = $User } $service = New-GoogleService @serviceParams try { Write-Verbose "Creating new Course '$Name'" $body = New-Object 'Google.Apis.Classroom.v1.Data.Course' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) { switch ($prop) { OwnerId { try { [decimal]$PSBoundParameters[$prop] | Out-Null } catch { if ($PSBoundParameters[$prop] -ceq 'me') { $PSBoundParameters[$prop] = $Script:PSGSuite.AdminEmail } elseif ($PSBoundParameters[$prop] -notlike "*@*.*") { $PSBoundParameters[$prop] = "$($PSBoundParameters[$prop])@$($Script:PSGSuite.Domain)" } } $body.$prop = $PSBoundParameters[$prop] } Default { $body.$prop = $PSBoundParameters[$prop] } } } if ($PSBoundParameters.Keys -notcontains 'OwnerId') { $body.OwnerId = $Script:PSGSuite.AdminEmail } $request = $service.Courses.Create($body) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'New-GSCourse' function New-GSCourseAlias { <# .SYNOPSIS Creates a course alias. .DESCRIPTION Creates a course alias. .PARAMETER Alias Alias string .PARAMETER CourseId Identifier of the course to alias. This identifier can be either the Classroom-assigned identifier or an alias. .PARAMETER Scope An alias uniquely identifies a course. It must be unique within one of the following scopes: * Domain - A domain-scoped alias is visible to all users within the alias creator's domain and can be created only by a domain admin. A domain-scoped alias is often used when a course has an identifier external to Classroom. * Project - A project-scoped alias is visible to any request from an application using the Developer Console project ID that created the alias and can be created by any project. A project-scoped alias is often used when an application has alternative identifiers. A random value can also be used to avoid duplicate courses in the event of transmission failures, as retrying a request will return ALREADY_EXISTS if a previous one has succeeded. .PARAMETER User The user to authenticate the request as .EXAMPLE New-GSCourseAlias -Alias "abc123" -CourseId 'architecture-101' -Scope Domain .EXAMPLE New-GSCourseAlias -Alias "d:abc123" -CourseId 'architecture-101' #> [OutputType('Google.Apis.Classroom.v1.Data.CourseAlias')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0)] [String] $Alias, [parameter(Mandatory = $true,Position = 1)] [String] $CourseId, [parameter(Mandatory = $false)] [ValidateSet('Domain','Project')] [String] $Scope = $(if($Alias -match "^p\:"){'Project'}else{'Domain'}), [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [String] $User = $Script:PSGSuite.AdminEmail ) Begin { $formatted = if ($Alias -match "^(d\:|p\:)") { $Alias $Scope = if ($Alias -match "^d\:") { 'Domain' } else { 'Project' } } else { switch ($Scope) { Domain { 'd:' + ($Alias -replace "^(d\:|p\:)","") } Project { 'p:' + ($Alias -replace "^(d\:|p\:)","") } } } } Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/classroom.courses' ServiceType = 'Google.Apis.Classroom.v1.ClassroomService' User = $User } $service = New-GoogleService @serviceParams try { Write-Verbose "Creating new Alias '$Alias' for Course '$CourseId' at '$Scope' scope" $body = New-Object 'Google.Apis.Classroom.v1.Data.CourseAlias' -Property @{ Alias = $formatted } $request = $service.Courses.Aliases.Create($body,$CourseId) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'New-GSCourseAlias' function New-GSCourseInvitation { <# .SYNOPSIS Creates a course invitation. .DESCRIPTION Creates a course invitation. .PARAMETER CourseId Identifier of the course to invite the user to. This identifier can be either the Classroom-assigned identifier or an alias. .PARAMETER UserId Identifier of the user to invite. The identifier can be one of the following: * the numeric identifier for the user * the email address of the user * the string literal "me", indicating the requesting user .PARAMETER Role Role to invite the user to have from the following: * STUDENT * TEACHER * OWNER .PARAMETER User The user to authenticate the request as .EXAMPLE New-GSCourseInvitation -CourseId philosophy-101 -UserId aristotle@athens.edu -Role TEACHER #> [OutputType('Google.Apis.Classroom.v1.Data.Invitation')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [String] $CourseId, [parameter(Mandatory = $false,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias('PrimaryEmail','Email','Mail')] [String[]] $UserId, [parameter(Mandatory = $false)] [ValidateSet('STUDENT','TEACHER','OWNER')] [String] $Role = 'STUDENT', [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [String] $User = $Script:PSGSuite.AdminEmail ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/classroom.rosters' ServiceType = 'Google.Apis.Classroom.v1.ClassroomService' User = $User } $service = New-GoogleService @serviceParams foreach ($U in $UserId) { try { if ( -not ($U -as [decimal])) { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } } Write-Verbose "Inviting User '$U' to Course '$CourseId' for Role '$Role'" $body = New-Object 'Google.Apis.Classroom.v1.Data.Invitation' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) { $body.$prop = $PSBoundParameters[$prop] } if ($PSBoundParameters.Keys -notcontains 'Role') { $body.Role = $Role } $request = $service.Invitations.Create($body) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'New-GSCourseInvitation' function New-GSStudentGuardianInvitation { <# .SYNOPSIS Creates a guardian invitation, and sends an email to the guardian asking them to confirm that they are the student's guardian. .DESCRIPTION Creates a guardian invitation, and sends an email to the guardian asking them to confirm that they are the student's guardian. .PARAMETER StudentId Identifier of the user to invite. The identifier can be one of the following: * the numeric identifier for the user * the email address of the user .PARAMETER GuardianEmail The email address of the guardian to invite. .PARAMETER User The user to authenticate the request as .EXAMPLE New-GSStudentGuardianInvitation -StudentId aristotle@athens.edu -GuardianEmail zeus@olympus.io .EXAMPLE Import-Csv .\Student_Guardian_List.csv | New-GSStudentGuardianInvitation Process a CSV with two columns containing headers "Student" and "Guardian" and send the invites accordingly, i.e. | StudentId | GuardianEmail | |:--------------------:|:---------------:| | aristotle@athens.edu | zeus@olympus.io | | plato@athens.edu | hera@olympus.io | #> [OutputType('Google.Apis.Classroom.v1.Data.GuardianInvitation')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [Alias('Student')] [String] $StudentId, [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [Alias('Guardian')] [String] $GuardianEmail, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [String] $User = $Script:PSGSuite.AdminEmail ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/classroom.guardianlinks.students' ServiceType = 'Google.Apis.Classroom.v1.ClassroomService' User = $User } $service = New-GoogleService @serviceParams try { if ( -not ($StudentId -as [decimal])) { if ($StudentId -ceq 'me') { $StudentId = $Script:PSGSuite.AdminEmail } elseif ($StudentId -notlike "*@*.*") { $StudentId = "$($StudentId)@$($Script:PSGSuite.Domain)" } } $body = New-Object 'Google.Apis.Classroom.v1.Data.GuardianInvitation' -Property @{ StudentId = $StudentId InvitedEmailAddress = $GuardianEmail } Write-Verbose "Inviting Guardian '$GuardianEmail' for Student '$StudentId'" $request = $service.UserProfiles.GuardianInvitations.Create($body,$StudentId) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'New-GSStudentGuardianInvitation' function Remove-GSCourse { <# .SYNOPSIS Removes an existing course. .DESCRIPTION Removes an existing course. .PARAMETER Id Identifier for this course assigned by Classroom. .PARAMETER User The user to authenticate the request as .EXAMPLE Remove-GSCourse -Id the-republic-s01 -Confirm:$false #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $true,Position = 0)] [Alias('Alias')] [String] $Id, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [String] $User = $Script:PSGSuite.AdminEmail ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/classroom.courses' ServiceType = 'Google.Apis.Classroom.v1.ClassroomService' User = $User } $service = New-GoogleService @serviceParams try { if ($PSCmdlet.ShouldProcess("Removing Course '$Id'")) { Write-Verbose "Removing Course '$Id'" $request = $service.Courses.Delete($Id) $request.Execute() Write-Verbose "Course '$Id' has been successfully removed" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Remove-GSCourse' function Remove-GSCourseAlias { <# .SYNOPSIS Removes a course alias. .DESCRIPTION Removes a course alias. .PARAMETER Alias Alias string .PARAMETER CourseId Identifier of the course to alias. This identifier can be either the Classroom-assigned identifier or an alias. .PARAMETER User The user to authenticate the request as .EXAMPLE Remove-GSCourseAlias -Alias "d:abc123" -CourseId 'architecture-101' #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $true,Position = 0)] [String] $Alias, [parameter(Mandatory = $true,Position = 1)] [String] $CourseId, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [String] $User = $Script:PSGSuite.AdminEmail ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/classroom.courses' ServiceType = 'Google.Apis.Classroom.v1.ClassroomService' User = $User } $service = New-GoogleService @serviceParams try { if ($PSCmdlet.ShouldProcess("Removing Alias '$Alias' for Course '$CourseId'")) { Write-Verbose "Removing Alias '$Alias' for Course '$CourseId'" $request = $service.Courses.Aliases.Delete($CourseId,$Alias) $request.Execute() Write-Verbose "Alias '$Alias' for Course '$CourseId' has been successfully removed" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Remove-GSCourseAlias' function Remove-GSCourseInvitation { <# .SYNOPSIS Deletes an invitation. .DESCRIPTION Deletes an invitation. .PARAMETER Id Identifier of the invitation to delete. .PARAMETER User The user to authenticate the request as .EXAMPLE Remove-GSCourseInvitation -Id $inviteId -Confirm:$false #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $true,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [String[]] $Id, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [String] $User = $Script:PSGSuite.AdminEmail ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/classroom.rosters' ServiceType = 'Google.Apis.Classroom.v1.ClassroomService' User = $User } $service = New-GoogleService @serviceParams foreach ($I in $Id) { try { if ($PSCmdlet.ShouldProcess("Removing Invitation '$I'")) { Write-Verbose "Removing Invitation '$I'" $request = $service.Invitations.Delete($I) $request.Execute() Write-Verbose "Invitation '$I' has been successfully removed" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Remove-GSCourseInvitation' function Remove-GSCourseParticipant { <# .SYNOPSIS Removes students and/or teachers from a course .DESCRIPTION Removes students and/or teachers from a course .PARAMETER CourseId Identifier of the course to remove participants from. This identifier can be either the Classroom-assigned identifier or an alias. .PARAMETER Student Identifier of the user. This identifier can be one of the following: * the numeric identifier for the user * the email address of the user * the string literal "me", indicating the requesting user .PARAMETER Teacher Identifier of the user. This identifier can be one of the following: * the numeric identifier for the user * the email address of the user * the string literal "me", indicating the requesting user .PARAMETER User The user to authenticate the request as .EXAMPLE Remove-GSCourseParticipant -CourseId 'architecture-101' -Student plato@athens.edu,aristotle@athens.edu -Teacher zeus@athens.edu #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $true,Position = 0)] [String] $CourseId, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias('PrimaryEmail','Email','Mail')] [String[]] $Student, [parameter(Mandatory = $false)] [String[]] $Teacher, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [String] $User = $Script:PSGSuite.AdminEmail ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/classroom.rosters' ServiceType = 'Google.Apis.Classroom.v1.ClassroomService' User = $User } $service = New-GoogleService @serviceParams foreach ($part in $Student) { try { if ( -not ($part -as [decimal])) { if ($part -ceq 'me') { $part = $Script:PSGSuite.AdminEmail } elseif ($part -notlike "*@*.*") { $part = "$($part)@$($Script:PSGSuite.Domain)" } } if ($PSCmdlet.ShouldProcess("Removing Student '$part' from Course '$CourseId'")) { Write-Verbose "Removing Student '$part' from Course '$CourseId'" $request = $service.Courses.Students.Delete($CourseId,$part) $request.Execute() Write-Verbose "Student '$part' has successfully been removed from Course '$CourseId'" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } foreach ($part in $Teacher) { try { try { [decimal]$part | Out-Null } catch { if ($part -ceq 'me') { $part = $Script:PSGSuite.AdminEmail } elseif ($part -notlike "*@*.*") { $part = "$($part)@$($Script:PSGSuite.Domain)" } } if ($PSCmdlet.ShouldProcess("Removing Teacher '$part' from Course '$CourseId'")) { Write-Verbose "Removing Teacher '$part' from Course '$CourseId'" $request = $service.Courses.Teachers.Delete($CourseId,$part) $request.Execute() Write-Verbose "Teacher '$part' has successfully been removed from Course '$CourseId'" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Remove-GSCourseParticipant' function Remove-GSStudentGuardian { <# .SYNOPSIS Removes a guardian. .DESCRIPTION Removes a guardian. .PARAMETER StudentId The identifier of the student to get guardian info for. The identifier can be one of the following: * the numeric identifier for the user * the email address of the user * the string literal "me", indicating the requesting user .PARAMETER GuardianId The id field from a Guardian. .PARAMETER User The user to authenticate the request as .EXAMPLE Remove-GSStudentGuardian -StudentId aristotle@athens.edu -GuardianId $guardianId Removes the guardian for artistotle@athens.edu. #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [Alias('Student')] [String] $StudentId, [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [Alias('Guardian')] [String] $GuardianId, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [String] $User = $Script:PSGSuite.AdminEmail ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/classroom.guardianlinks.students' ServiceType = 'Google.Apis.Classroom.v1.ClassroomService' User = $User } $service = New-GoogleService @serviceParams try { if ( -not ($StudentId -as [decimal])) { if ($StudentId -ceq 'me') { $StudentId = $Script:PSGSuite.AdminEmail } elseif ($StudentId -notlike "*@*.*") { $StudentId = "$($StudentId)@$($Script:PSGSuite.Domain)" } } if ($PSCmdlet.ShouldProcess("Removing Guardian '$GuardianId' from Student '$StudentId'")) { Write-Verbose "Removing Guardian '$GuardianId' from Student '$StudentId'" $request = $service.UserProfiles.Guardians.Delete($StudentId,$GuardianId) $request.Execute() } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Remove-GSStudentGuardian' function Revoke-GSStudentGuardianInvitation { <# .SYNOPSIS Revokes a student guardian invitation. .DESCRIPTION Revokes a student guardian invitation. This method returns the following error codes: * PERMISSION_DENIED if the current user does not have permission to manage guardians, if guardians are not enabled for the domain in question or for other access errors. * FAILED_PRECONDITION if the guardian link is not in the PENDING state. * INVALID_ARGUMENT if the format of the student ID provided cannot be recognized (it is not an email address, nor a user_id from this API), or if the passed GuardianInvitation has a state other than COMPLETE, or if it modifies fields other than state. * NOT_FOUND if the student ID provided is a valid student ID, but Classroom has no record of that student, or if the id field does not refer to a guardian invitation known to Classroom. .PARAMETER StudentId The ID of the student whose guardian invitation is to be revoked. The identifier can be one of the following: * the numeric identifier for the user * the email address of the user .PARAMETER InvitationId The id field of the GuardianInvitation to be revoked. .PARAMETER User The user to authenticate the request as .EXAMPLE Revoke-GSStudentGuardianInvitation -StudentId aristotle@athens.edu -InvitationId $invitationId .EXAMPLE Import-Csv .\Student_Guardian_List_To_Revoke.csv | Revoke-GSStudentGuardianInvitation Process a CSV with two columns containing headers "Student" and "Guardian" and revokes the invites accordingly, i.e. | StudentId | InvitationId | |:--------------------:|:------------------:| | aristotle@athens.edu | 198okj4k9827872177 | | plato@athens.edu | 09120uuip21ru0ff0u | #> [OutputType('Google.Apis.Classroom.v1.Data.GuardianInvitation')] [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [Alias('Student')] [String] $StudentId, [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [Alias('Invitation','InviteId','Invite')] [String[]] $InvitationId, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [String] $User = $Script:PSGSuite.AdminEmail ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/classroom.guardianlinks.students' ServiceType = 'Google.Apis.Classroom.v1.ClassroomService' User = $User } $service = New-GoogleService @serviceParams if ( -not ($StudentId -as [decimal])) { if ($StudentId -ceq 'me') { $StudentId = $Script:PSGSuite.AdminEmail } elseif ($StudentId -notlike "*@*.*") { $StudentId = "$($StudentId)@$($Script:PSGSuite.Domain)" } } foreach ($invId in $InvitationId) { try { if ($PSCmdlet.ShouldProcess("Revoking Guardian Invitation '$invId' for Student '$StudentId'")) { Write-Verbose "Revoking Guardian Invitation '$invId' for Student '$StudentId'" $body = New-Object 'Google.Apis.Classroom.v1.Data.GuardianInvitation' -Property @{ State = "COMPLETE" } $request = $service.UserProfiles.GuardianInvitations.Patch($body,$StudentId,$invId) $request.UpdateMask = "state" $request.Execute() } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Revoke-GSStudentGuardianInvitation' function Update-GSCourse { <# .SYNOPSIS Updates an existing course. .DESCRIPTION Updates an existing course. .PARAMETER Id Identifier for this course assigned by Classroom. .PARAMETER Name Name of the course. For example, "10th Grade Biology". The name is required. It must be between 1 and 750 characters and a valid UTF-8 string. .PARAMETER OwnerId The identifier of the owner of a course. When specified as a parameter of a create course request, this field is required. The identifier can be one of the following: * the numeric identifier for the user * the email address of the user * the string literal "me", indicating the requesting user .PARAMETER Section Section of the course. For example, "Period 2". If set, this field must be a valid UTF-8 string and no longer than 2800 characters. .PARAMETER DescriptionHeading Optional heading for the description. For example, "Welcome to 10th Grade Biology." If set, this field must be a valid UTF-8 string and no longer than 3600 characters. .PARAMETER Description Optional description. For example, "We'll be learning about the structure of living creatures from a combination of textbooks, guest lectures, and lab work. Expect to be excited!" If set, this field must be a valid UTF-8 string and no longer than 30,000 characters. .PARAMETER Room Optional room location. For example, "301". If set, this field must be a valid UTF-8 string and no longer than 650 characters. .PARAMETER CourseState State of the course. If unspecified, the default state is PROVISIONED Available values are: * ACTIVE - The course is active. * ARCHIVED - The course has been archived. You cannot modify it except to change it to a different state. * PROVISIONED - The course has been created, but not yet activated. It is accessible by the primary teacher and domain administrators, who may modify it or change it to the ACTIVE or DECLINED states. A course may only be changed to PROVISIONED if it is in the DECLINED state. * DECLINED - The course has been created, but declined. It is accessible by the course owner and domain administrators, though it will not be displayed in the web UI. You cannot modify the course except to change it to the PROVISIONED state. A course may only be changed to DECLINED if it is in the PROVISIONED state. .PARAMETER User The user to authenticate the request as .EXAMPLE Update-GSCourse -Id the-republic-s01 -Name "The Rebublic 101" #> [OutputType('Google.Apis.Classroom.v1.Data.Course')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0)] [Alias('Alias')] [String] $Id, [parameter(Mandatory = $false)] [ValidateLength(1,750)] [String] $Name, [parameter(Mandatory = $false)] [Alias('Teacher')] [String] $OwnerId, [parameter(Mandatory = $false)] [ValidateLength(0,2800)] [String] $Section, [parameter(Mandatory = $false)] [ValidateLength(0,3600)] [Alias('Heading')] [String] $DescriptionHeading, [parameter(Mandatory = $false)] [ValidateLength(0,30000)] [String] $Description, [parameter(Mandatory = $false)] [ValidateLength(0,650)] [String] $Room, [parameter(Mandatory = $false)] [Alias('Status')] [ValidateSet('PROVISIONED','ACTIVE','ARCHIVED','DECLINED')] [String] $CourseState, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [String] $User = $Script:PSGSuite.AdminEmail ) Begin { $UpdateMask = @() } Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/classroom.courses' ServiceType = 'Google.Apis.Classroom.v1.ClassroomService' User = $User } $service = New-GoogleService @serviceParams try { Write-Verbose "Updating Course ID '$Id'" $body = New-Object 'Google.Apis.Classroom.v1.Data.Course' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) { switch ($prop) { Id {} OwnerId { $UpdateMask += $($prop.Substring(0,1).ToLower() + $prop.Substring(1)) try { [decimal]$PSBoundParameters[$prop] | Out-Null } catch { if ($PSBoundParameters[$prop] -ceq 'me') { $PSBoundParameters[$prop] = $Script:PSGSuite.AdminEmail } elseif ($PSBoundParameters[$prop] -notlike "*@*.*") { $PSBoundParameters[$prop] = "$($PSBoundParameters[$prop])@$($Script:PSGSuite.Domain)" } } $body.$prop = $PSBoundParameters[$prop] } Default { $UpdateMask += $($prop.Substring(0,1).ToLower() + $prop.Substring(1)) $body.$prop = $PSBoundParameters[$prop] } } } $request = $service.Courses.Patch($body,$Id) $request.UpdateMask = $($UpdateMask -join ",") $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Update-GSCourse' function Export-PSGSuiteConfig { <# .SYNOPSIS Allows you to export an unecrypted PSGSuite config in a portable JSON string format. Useful for moving a config to a new machine or storing the full as an encrypted string in your CI/CD / Automation tools. .DESCRIPTION Allows you to export an unecrypted PSGSuite config in a portable JSON string format. Useful for moving a config to a new machine or storing the full as an encrypted string in your CI/CD / Automation tools. .PARAMETER Path The path you would like to save the JSON file to. Defaults to a named path in the current directory. .PARAMETER ConfigName The config that you would like to export. Defaults to the currently loaded config. .EXAMPLE Export-PSGSuiteConfig -ConfigName Personal -Path ".\PSGSuite_personal_config.json" Exports the config named 'Personal' to the path specified. #> [CmdletBinding()] Param ( [parameter(Mandatory = $false,Position = 0)] [Alias('OutPath','OutFile','JsonPath')] [String] $Path, [parameter(Mandatory = $false)] [String] $ConfigName = $script:PSGSuite.ConfigName ) Begin { $baseConf = if ($PSBoundParameters.Keys -contains 'ConfigName'){ Get-PSGSuiteConfig -ConfigName $ConfigName -NoImport -PassThru -Verbose:$false } else { Show-PSGSuiteConfig -Verbose:$false } if ($PSBoundParameters.Keys -notcontains 'Path') { $Path = (Join-Path $PWD.Path "PSGSuite_$($baseConf.AdminEmail)_$($baseConf.ConfigName).json") } } Process { try { Write-Verbose "Exporting config '$ConfigName' to path: $Path" $baseConf | Select-Object ConfigName,P12Key,ClientSecrets,AppEmail,AdminEmail,CustomerId,Domain,Preference | ConvertTo-Json -Depth 5 -Compress -Verbose:$false | Set-Content -Path $Path -Verbose:$false } catch { $PSCmdlet.ThrowTerminatingError($_) } } } Export-ModuleMember -Function 'Export-PSGSuiteConfig' function Get-GSChatConfig { <# .SYNOPSIS Returns the specified Chat space and webhook dictionaries from the PSGSuite config to use with Send-GSChatMessage .DESCRIPTION Returns the specified Chat space and webhook dictionaries from the PSGSuite config to use with Send-GSChatMessage .PARAMETER WebhookName The key that the Webhook Url is stored as in the Config. If left blank, returns the full Chat configuration from the Config .PARAMETER SpaceName The key that the Space ID is stored as in the Config. If left blank, returns the full Chat configuration from the Config .PARAMETER ConfigName The name of the Config to return the Chat config items from .EXAMPLE Send-GSChatMessage -Text "Testing webhook" -Webhook (Get-GSChatConfig MyRoom) Sends a Chat message with text to the Webhook Url named 'MyRoom' found in the config #> [CmdletBinding(DefaultParameterSetName = "Webhooks")] Param ( [parameter(Mandatory = $false,Position = 0,ParameterSetName = "Webhooks")] [String[]] $WebhookName, [parameter(Mandatory = $false,Position = 0,ParameterSetName = "Spaces")] [String[]] $SpaceName, [parameter(Mandatory = $false,Position = 1)] [String[]] $ConfigName ) if ($PSBoundParameters.Keys -contains 'ConfigName') { $currentConfig = Get-PSGSuiteConfig -ConfigName $ConfigName -PassThru -NoImport } else { $currentConfig = Get-PSGSuiteConfig -PassThru } switch ($PSCmdlet.ParameterSetName) { Webhooks { if ($PSBoundParameters.Keys -contains 'WebhookName') { foreach ($hook in $WebhookName) { Write-Verbose "Getting webhook for '$hook' from ConfigName '$($currentConfig.ConfigName)'" if ($found = $currentConfig.Chat['Webhooks'][$hook]) { $found } else { Write-Error "$hook was not found in the Webhook dictionary stored in ConfigName '$($currentConfig.ConfigName)'!" } } } else { Write-Verbose "Getting full Chat config from ConfigName '$($currentConfig.ConfigName)'" $currentConfig.Chat } } Spaces { foreach ($hook in $SpaceName) { Write-Verbose "Getting space Id for '$hook' from ConfigName '$($currentConfig.ConfigName)'" if ($found = $currentConfig.Chat['Spaces'][$hook]) { $found } else { Write-Error "$hook was not found in the Spaces dictionary stored in ConfigName '$($currentConfig.ConfigName)'!" } } } } } Export-ModuleMember -Function 'Get-GSChatConfig' function Get-PSGSuiteConfig { <# .SYNOPSIS Loads the specified PSGSuite config .DESCRIPTION Loads the specified PSGSuite config .PARAMETER ConfigName The config name to load .PARAMETER Path The path of the config to load if non-default. This can be used to load either a legacy XML config from an older version of PSGSuite or a specific .PSD1 config created with version 2.0.0 or greater .PARAMETER Scope The config scope to load .PARAMETER PassThru If specified, returns the config after loading it .PARAMETER NoImport If $true, just returns the specified config but does not impart it in the current session. .EXAMPLE Get-PSGSuiteConfig personalDomain -PassThru This will load the config named "personalDomain" and return it as a PSObject. #> [cmdletbinding(DefaultParameterSetName = "ConfigurationModule")] Param ( [parameter(Mandatory = $false,Position = 0,ParameterSetName = "ConfigurationModule")] [String] $ConfigName, [Parameter(Mandatory = $false,ParameterSetName = "Path")] [ValidateScript( {Test-Path $_})] [String] $Path, [Parameter(Mandatory = $false,Position = 1)] [ValidateSet("User", "Machine", "Enterprise", $null)] [string] $Scope = $Script:ConfigScope, [Parameter(Mandatory = $false)] [Switch] $PassThru, [Parameter(Mandatory = $false)] [Switch] $NoImport ) Process { $script:ConfigScope = $Scope switch ($PSCmdlet.ParameterSetName) { ConfigurationModule { $fullConf = Import-SpecificConfiguration -CompanyName 'SCRT HQ' -Name 'PSGSuite' -Scope $Script:ConfigScope if (!$ConfigName) { $choice = $fullConf["DefaultConfig"] Write-Verbose "Importing default config: $choice" } else { $choice = $ConfigName Write-Verbose "Importing config: $choice" } $encConf = [PSCustomObject]($fullConf[$choice]) } Path { $encConf = switch ((Get-Item -Path $Path).Extension) { '.xml' { Import-Clixml -Path $Path $choice = "LegacyXML" } '.psd1' { Import-SpecificConfiguration -Path $Path $choice = "CustomConfigurationFile" } } } } $decryptParams = @{ ConfigName = $choice } if ($Path) { $decryptParams['ConfigPath'] = $Path } $decryptedConfig = $encConf | Get-GSDecryptedConfig @decryptParams Write-Verbose "Retrieved configuration '$choice'" if (!$NoImport) { $script:PSGSuite = $decryptedConfig } if ($PassThru) { $decryptedConfig } } } Export-ModuleMember -Function 'Get-PSGSuiteConfig' function Import-PSGSuiteConfig { <# .SYNOPSIS Allows you to import an unecrypted PSGSuite config from a portable JSON string format, typically created with Export-PSGSuiteConfig. Can also import directly from a PSCustomObject. Useful for moving a config to a new machine or storing the full as an encrypted string in your CI/CD / Automation tools. .DESCRIPTION Allows you to import an unecrypted PSGSuite config from a portable JSON string format, typically created with Export-PSGSuiteConfig. Can also import directly from a PSCustomObject. Useful for moving a config to a new machine or storing the full as an encrypted string in your CI/CD / Automation tools. .PARAMETER Json The Json string to import. .PARAMETER Path The path of the Json file you would like import. .PARAMETER Object The PSCustomObject you would like to import .PARAMETER Temporary If $true, the imported config is not stored in the config file and the imported config persists only for the current session. .PARAMETER PassThru If $true, outputs the resulting config object to the pipeline. .EXAMPLE Import-Module PSGSuite -MinimumVersion 2.22.0 Import-PSGSuiteConfig -Json '$(PSGSuiteConfigJson)' -Temporary Azure Pipelines inline script task that uses a Secure Variable named 'PSGSuiteConfigJson' with the Config JSON string stored in it, removing the need to include credential or key files anywhere. .EXAMPLE [PSCustomObject]$PSGSuiteSettings = [PSCustomObject]@{ P12KeyObject = Get-AutomationCertificate -name 'gcert.p12' AppEmail = Get-AutomationVariable -name 'gappemail' AdminEmail = Get-AutomationVariable -name 'gadminemail' } Import-PSGsuiteConfig -Object $PSGSuiteSettings -Temporary Some properties can't be serialized in JSON, this is another way to store secrets in your automation tool (this example shows Azure Automation) #> [CmdletBinding(DefaultParameterSetName = "Json")] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ParameterSetName = "Json")] [Alias('J')] [String] $Json, [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = "Path")] [Alias('P')] [String] $Path, [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = "Object")] [Alias('O')] [PSCustomObject] $Object, [parameter(Mandatory = $false)] [Alias('Temp','T')] [Switch] $Temporary, [parameter(Mandatory = $false)] [Switch] $PassThru ) Process { try { switch ($PSCmdlet.ParameterSetName) { Path { Write-Verbose "Importing config from path: $Path" $script:PSGSuite = (ConvertFrom-Json (Get-Content $Path -Raw)) | Get-GSDecryptedConfig -ConfigName 'ImportPath' } Json { Write-Verbose "Importing config from Json string" $script:PSGSuite = (ConvertFrom-Json $Json) | Get-GSDecryptedConfig -ConfigName 'ImportJSON' } Object { Write-Verbose "Importing config from Object" $script:PSGSuite = $Object | Get-GSDecryptedConfig -ConfigName 'ImportObject' } } if (-not $Temporary) { Write-Verbose "Saving imported config" $script:PSGSuite | Set-PSGSuiteConfig } if ($PassThru) { return $script:PSGSuite } } catch { $PSCmdlet.ThrowTerminatingError($_) } } } Export-ModuleMember -Function 'Import-PSGSuiteConfig' function Set-PSGSuiteConfig { <# .SYNOPSIS Creates or updates a config .DESCRIPTION Creates or updates a config .PARAMETER ConfigName The friendly name for the config you are creating or updating .PARAMETER P12KeyPath The path to the P12 Key file downloaded from the Google Developer's Console. If both P12KeyPath and ClientSecretsPath are specified, P12KeyPath takes precedence. .PARAMETER P12Key The P12Key in byte array format. If the actual P12Key is present on the config, the P12KeyPath is not needed. The config will auto-update with this value after running any command, if P12KeyPath is filled and this value is not already present. .PARAMETER P12KeyPassword The password for the P12 Key file. If not specified the default of 'notasecret' will be used and this config value will not be set. This is only needed in the case where the P12 file has been manually rexported with a custom password .PARAMETER JSONServiceAccountKeyPath The path to the Service Account JSON file downloaded from the Google Developer's Console. .PARAMETER JSONServiceAccountKey The string contents of the Serivce Account JSON file downloaded from the Google Developer's Console. .PARAMETER ClientSecretsPath The path to the Client Secrets JSON file downloaded from the Google Developer's Console. Using the ClientSecrets JSON will prompt the user to complete OAuth2 authentication in their browser on the first run and store the retrieved Refresh and Access tokens in the user's home directory. The config will auto-update with this value after running any command, if ClientSecretsPath is filled and this value is not already present. If JSONServiceAccountKeyPath or P12KeyPath is also specified, ClientSecretsPath will be ignored. .PARAMETER ClientSecrets The string contents of the Client Secrets JSON file downloaded from the Google Developer's Console. Using the ClientSecrets JSON will prompt the user to complete OAuth2 authentication in their browser on the first run and store the retrieved Refresh and Access tokens in the user's home directory. If JSONServiceAccountKeyPath or P12KeyPath is also specified, ClientSecrets will be ignored. .PARAMETER AppEmail The application email from the Google Developer's Console. This typically looks like the following: myProjectName@myProject.iam.gserviceaccount.com .PARAMETER AdminEmail The email of the Google Admin running the functions. This will typically be your email. .PARAMETER CustomerID The Customer ID for your customer. If unknown, you can retrieve it by running Get-GSUser after creating a base config with at least either the P12KeyPath or ClientSecretsPath, the AppEmail and the AdminEmail. .PARAMETER Domain The domain that you primarily manage for this CustomerID .PARAMETER Preference Some functions allow you to specify whether you are running in the context of the customer or a specific domain in the customer's realm. This allows you to set your preference. Available values are: * CustomerID * Domain .PARAMETER ServiceAccountClientID The Service Account's Client ID from the Google Developer's Console. This is optional and is only used as a reference for yourself to prevent needing to check the Developer's Console for the ID when verifying API Client Access. .PARAMETER Webhook Chat Webhooks to add to the config. .PARAMETER Space Chat spaces to add to the config. .PARAMETER Scope The scope at which you would like to set this config. Available values are: * Machine (this would create the config in a location accessible by all users on the machine) * Enterprise (this would create the config in the Roaming AppData folder for the user or it's *nix equivalent) * User (this would create the config in the Local AppData folder for the user or it's *nix equivalent) .PARAMETER SetAsDefaultConfig If passed, sets the ConfigName as the default config to load on module import .PARAMETER NoImport The default behavior when using Set-PSGSuiteConfig is that the new/updated config is imported as active. If -NoImport is passed, this saves the config but retains the previously loaded config as active. .EXAMPLE Set-PSGSuiteConfig -ConfigName "personal" -P12KeyPath C:\Keys\PersonalKey.p12 -AppEmail "myProjectName@myProject.iam.gserviceaccount.com" -AdminEmail "admin@domain.com" -CustomerID "C83030001" -Domain "domain.com" -Preference CustomerID -ServiceAccountClientID 1175798883298324983498 -SetAsDefaultConfig This builds a config names "personal" and sets it as the default config #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")] [cmdletbinding()] Param ( [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [ValidateScript( { if ($_ -eq "DefaultConfig") { throw "You must specify a ConfigName other than 'DefaultConfig'. That is a reserved value." } elseif ($_ -notmatch '^[a-zA-Z]+[a-zA-Z0-9]*$') { throw "You must specify a ConfigName that starts with a letter and does not contain any spaces, otherwise the Configuration will break" } else { $true } })] [string] $ConfigName = $Script:ConfigName, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [string] $P12KeyPath, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Byte[]] $P12Key, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [SecureString] $P12KeyPassword, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [string] $JSONServiceAccountKeyPath, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [string] $JSONServiceAccountKey, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [string] $ClientSecretsPath, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [string] $ClientSecrets, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [string] $AppEmail, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [string] $AdminEmail, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [string] $CustomerID, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [string] $Domain, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [ValidateSet("CustomerID","Domain")] [string] $Preference, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [string] $ServiceAccountClientID, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Hashtable[]] $Webhook, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Hashtable[]] $Space, [parameter(Mandatory = $false)] [ValidateSet("User", "Machine", "Enterprise", $null)] [string] $Scope = $script:ConfigScope, [parameter(Mandatory = $false)] [switch] $SetAsDefaultConfig, [parameter(Mandatory = $false)] [switch] $NoImport ) Process { $script:ConfigScope = $Scope $configHash = Import-SpecificConfiguration -CompanyName 'SCRT HQ' -Name 'PSGSuite' if (!$ConfigName) { $ConfigName = if ($configHash["DefaultConfig"]){ $configHash["DefaultConfig"] } else { "default" $configHash["DefaultConfig"] = "default" } } Write-Verbose "Setting config name '$ConfigName'" $configParams = @('P12Key','P12KeyPath','P12KeyPassword','JSONServiceAccountKeyPath','JSONServiceAccountKey','ClientSecretsPath','ClientSecrets','AppEmail','AdminEmail','CustomerID','Domain','Preference','ServiceAccountClientID','Webhook','Space') if ($SetAsDefaultConfig -or !$configHash["DefaultConfig"]) { $configHash["DefaultConfig"] = $ConfigName } if (!$configHash[$ConfigName]) { $configHash.Add($ConfigName,(@{})) } foreach ($key in ($PSBoundParameters.Keys | Where-Object {$configParams -contains $_})) { switch ($key) { P12Key { if (-not $_p12Key) { $_p12Key = @() } if ($P12Key.Count -gt 1) { $_p12Key = $P12Key } else { $_p12Key += $P12Key } } P12KeyPath { if (-not [System.String]::IsNullOrWhiteSpace($PSBoundParameters[$key].Trim())) { $configHash["$ConfigName"][$key] = (Invoke-GSEncrypt $PSBoundParameters[$key]) $configHash["$ConfigName"]['P12Key'] = ([System.IO.File]::ReadAllBytes($PSBoundParameters[$key])) } } JSONServiceAccountKeyPath { if (-not [System.String]::IsNullOrWhiteSpace($PSBoundParameters[$key].Trim())) { $configHash["$ConfigName"][$key] = (Invoke-GSEncrypt $PSBoundParameters[$key]) $configHash["$ConfigName"]['JSONServiceAccountKey'] = (Invoke-GSEncrypt $(Get-Content $PSBoundParameters[$key] -Raw)) } } ClientSecretsPath { if (-not [System.String]::IsNullOrWhiteSpace($PSBoundParameters[$key].Trim())) { $configHash["$ConfigName"][$key] = (Invoke-GSEncrypt $PSBoundParameters[$key]) $configHash["$ConfigName"]['ClientSecrets'] = (Invoke-GSEncrypt $(Get-Content $PSBoundParameters[$key] -Raw)) } } Webhook { if ($configHash["$ConfigName"].Keys -notcontains 'Chat') { $configHash["$ConfigName"]['Chat'] = @{ Webhooks = @{} Spaces = @{} } } foreach ($cWebhook in $PSBoundParameters[$key]) { foreach ($cWebhookKey in $cWebhook.Keys) { $configHash["$ConfigName"]['Chat']['Webhooks'][$cWebhookKey] = (Invoke-GSEncrypt $cWebhook[$cWebhookKey]) } } } Space { if ($configHash["$ConfigName"].Keys -notcontains 'Chat') { $configHash["$ConfigName"]['Chat'] = @{ Webhooks = @{} Spaces = @{} } } $configHash["$ConfigName"]['Chat']['Spaces'] = @{} foreach ($cWebhook in $PSBoundParameters[$key]) { foreach ($cWebhookKey in $cWebhook.Keys) { $configHash["$ConfigName"]['Chat']['Spaces'][$cWebhookKey] = (Invoke-GSEncrypt $cWebhook[$cWebhookKey]) } } } default { $configHash["$ConfigName"][$key] = (Invoke-GSEncrypt $PSBoundParameters[$key]) } } } } End { if ($_p12Key) { $configHash["$ConfigName"]['P12Key'] = $_p12Key } $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 } } } Export-ModuleMember -Function 'Set-PSGSuiteConfig' function Show-PSGSuiteConfig { <# .SYNOPSIS Returns the currently loaded config .DESCRIPTION Returns the currently loaded config .EXAMPLE Show-PSGSuiteConfig #> [CmdletBinding()] Param() Write-Verbose "Showing current PSGSuite config" $script:PSGSuite } Export-ModuleMember -Function 'Show-PSGSuiteConfig' function Switch-PSGSuiteConfig { <# .SYNOPSIS Switches the active config .DESCRIPTION Switches the active config .PARAMETER ConfigName The friendly name of the config you would like to set as active for the session .PARAMETER Domain The domain name for the config you would like to set as active for the session .PARAMETER SetToDefault If passed, also sets the specified config as the default so it's loaded on the next module import .EXAMPLE Switch-PSGSuiteConfig newCustomer Switches the config to the "newCustomer" config #> [CmdletBinding(DefaultParameterSetName = "ConfigName",PositionalBinding = $false)] Param ( [parameter(Mandatory = $true,ParameterSetName = "Domain")] [ValidateNotNullOrEmpty()] [String] $Domain, [parameter(Mandatory = $false)] [switch] $SetToDefault ) DynamicParam { $attribute = New-Object System.Management.Automation.ParameterAttribute $attribute.Position = 0 $attribute.Mandatory = $true $attribute.ParameterSetName = "ConfigName" $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $attributeCollection.Add($attribute) $names = Get-PSGSuiteConfigNames -Verbose:$false $attributeCollection.Add((New-Object System.Management.Automation.ValidateSetAttribute($names))) $parameter = New-Object System.Management.Automation.RuntimeDefinedParameter('ConfigName', [String], $attributeCollection) $paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $paramDictionary.Add('ConfigName', $parameter) return $paramDictionary } Process { $ConfigName = $PSBoundParameters['ConfigName'] if ($script:PSGSuite.Domain -eq $Domain) { Write-Verbose "Current config is already set to domain '$Domain' --- retaining current config. If you would like to import a different config for the same domain, please use the -ConfigName parameter instead" if ($SetToDefault) { Write-Verbose "Setting config name '$($script:PSGSuite.ConfigName)' for domain '$($script:PSGSuite.Domain)' as default" Set-PSGSuiteConfig -ConfigName $($script:PSGSuite.ConfigName) -SetAsDefaultConfig -Verbose:$false } } elseif ($script:PSGSuite.ConfigName -eq $ConfigName) { Write-Verbose "Current config is already set to '$ConfigName' --- retaining current config" if ($SetToDefault) { Write-Verbose "Setting config name '$($script:PSGSuite.ConfigName)' for domain '$($script:PSGSuite.Domain)' as default" Set-PSGSuiteConfig -ConfigName $($script:PSGSuite.ConfigName) -SetAsDefaultConfig -Verbose:$false } } else { $fullConf = Import-SpecificConfiguration -CompanyName 'SCRT HQ' -Name 'PSGSuite' -Scope $Script:ConfigScope -Verbose:$false $defaultConfigName = $fullConf['DefaultConfig'] $choice = switch ($PSCmdlet.ParameterSetName) { Domain { Write-Verbose "Switching active domain to '$Domain'" $fullConf.Keys | Where-Object {(Decrypt $fullConf[$_]['Domain']) -eq $Domain} } ConfigName { Write-Verbose "Switching active config to '$ConfigName'" $fullConf.Keys | Where-Object {$_ -eq $ConfigName} } } if ($choice) { $script:PSGSuite = [PSCustomObject]($fullConf[$choice]) | Get-GSDecryptedConfig -ConfigName $choice if ($SetToDefault) { if ($defaultConfigName -ne $choice) { Write-Verbose "Setting config name '$choice' for domain '$($script:PSGSuite.Domain)' as default" Set-PSGSuiteConfig -ConfigName $choice -SetAsDefaultConfig -Verbose:$false $env:PSGSuiteDefaultDomain = $script:PSGSuite.Domain [Environment]::SetEnvironmentVariable("PSGSuiteDefaultDomain", $script:PSGSuite.Domain, "User") } else { Write-Warning "Config name '$choice' for domain '$($script:PSGSuite.Domain)' is already set to default --- no action taken" } } } else { switch ($PSCmdlet.ParameterSetName) { Domain { Write-Warning "No config found for domain '$Domain'! Retaining existing config for domain '$($script:PSGSuite.Domain)'" } ConfigName { Write-Warning "No config named '$ConfigName' found! Retaining existing config '$($script:PSGSuite.ConfigName)'" } } } } } } Export-ModuleMember -Function 'Switch-PSGSuiteConfig' Function Get-GSContactList { <# .SYNOPSIS Gets all contacts for the specified user .DESCRIPTION Gets all contacts for the specified user .PARAMETER User The primary email or UserID of the user. 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. Defaults to the AdminEmail in the config. .EXAMPLE Get-GSContactList -User user@domain.com #> [cmdletbinding()] Param ( [parameter(Mandatory = $false, Position = 0, ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail", "UserKey", "Mail")] [ValidateNotNullOrEmpty()] [string[]] $User = $Script:PSGSuite.AdminEmail ) Process { foreach ($U in $User) { try { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } $Token = Get-GSToken -Scopes 'https://www.google.com/m8/feeds' -AdminEmail $U $Uri = "https://www.google.com/m8/feeds/contacts/$($U)/full?max-results=5000" $headers = @{ Authorization = "Bearer $($Token)" 'GData-Version' = '3.0' } Write-Verbose "Getting all contacts for user '$U'" $Raw = @() do { $Response = Invoke-WebRequest -Method Get -Uri ([Uri]$Uri) -Headers $headers -ContentType 'application/xml' -Verbose:$false $Feed = [xml]$Response.Content $Raw += $feed.feed.entry $Uri = $Feed.Feed.Link | Where-Object {$_.rel -eq "next"} | Select-Object -ExpandProperty Href Write-Verbose "Retrieved $($Raw.Count) contacts..." } until (-not $Uri) If ($Raw) { ForEach ($i in $Raw) { [PSCustomObject]@{ User = $U Id = ($i.id.Split("/")[-1]) Title = $i.title FullName = $i.name.fullName GivenName = $i.name.givenName FamilyName = $i.name.familyName EmailAddresses = $(if($i.email.address){$i.email.address}else{$null}) PhoneNumber = $i.phonenumber Updated = $i.updated Edited = $i.edited.'#text' Path = $(if($i.email.rel){$i.email.rel}else{$null}) Etag = $i.etag FullObject = $i } } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Get-GSContactList' Function Remove-GSContact { <# .SYNOPSIS Removes the specified contact .DESCRIPTION Removes the specified contact .PARAMETER ContactID The ContactID to be removed. .PARAMETER User The primary email or UserID of the user. 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. Defaults to the AdminEmail in the config. .PARAMETER Etag The Etag string from a Get-GSContactList object. Used to ensure that no changes have been made to the contact since it was viewed in order to prevent data loss. Defaults to special Etag value *, which can be used to bypass this verification and process the update regardless of updates from other clients. .EXAMPLE Recommended to use Get-GSContactList to find and pipe desired contacts to Remove-GSContact: Get-GSContactList -User user@domain.com | Where-Object {"@baddomain.com" -match $_.EmailAddresses} | Remove-GSContact Removes all contacts for user@domain.com that have an email address on the baddomain.com domain. #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "High")] Param ( [parameter(Mandatory = $false, Position = 0, ValueFromPipelineByPropertyName = $true)] [Alias("Id")] [string[]] $ContactId, [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail", "UserKey", "Mail")] [ValidateNotNullOrEmpty()] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [string] $Etag = '*' ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $Token = Get-GSToken -Scopes 'https://www.google.com/m8/feeds' -AdminEmail $User $headers = @{ Authorization = "Bearer $($Token)" 'GData-Version' = '3.0' 'If-Match' = $Etag } foreach ($Id in $ContactID) { if ($PSCmdlet.ShouldProcess("Removing contact ID '$Id' for $User")) { Write-Verbose "Removing contact ID '$Id' for $User" try { $Uri = "https://www.google.com/m8/feeds/contacts/$($User)/full/$($Id)" $Response = Invoke-WebRequest -Method "Delete" -Uri ([Uri]$Uri) -Headers $headers -Verbose:$false If ($Response.StatusCode -eq "200") { Write-Verbose "Successfully deleted contact ID '$Id' for $User" } Else { Write-Verbose "HTTP $($Response.StatusCode): $($Response.StatusDescription)" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } } Export-ModuleMember -Function 'Remove-GSContact' function Get-GSCustomer { <# .SYNOPSIS Retrieves a customer .DESCRIPTION Retrieves a customer .PARAMETER CustomerKey Id of the Customer to be retrieved .EXAMPLE Get-GSCustomer (Get-GSUser).CustomerId #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Customer')] [CmdletBinding()] Param( [Parameter(Position = 0,ValueFromPipeline,ValueFromPipelineByPropertyName)] [Alias('CustomerId')] [String] $CustomerKey = $Script:PSGSuite.CustomerId ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.customer' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { Write-Verbose "Getting Customer '$CustomerKey'" $request = $service.Customers.Get($CustomerKey) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSCustomer' function Update-GSCustomer { <# .SYNOPSIS Updates a customer using patch semantics. .DESCRIPTION Updates a customer using patch semantics. .PARAMETER CustomerKey Id of the Customer to be updated. .PARAMETER AlternateEmail The customer's secondary contact email address. This email address cannot be on the same domain as the customerDomain. .PARAMETER CustomerDomain The customer's primary domain name string. Do not include the www prefix when creating a new customer. .PARAMETER Language The customer's ISO 639-2 language code. The default value is en-US. .PARAMETER PhoneNumber The customer's contact phone number in E.164 format. .PARAMETER PostalAddress The customer's postal address information. Must be type [Google.Apis.Admin.Directory.directory_v1.Data.CustomerPostalAddress]. Use helper function Add-GSCustomerPostalAddress to create the correct type easily. .EXAMPLE Get-GSCustomer (Get-GSUser).CustomerId #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Customer')] [CmdletBinding()] Param( [Parameter(Position = 0,ValueFromPipeline,ValueFromPipelineByPropertyName)] [Alias('CustomerId')] [String] $CustomerKey = $Script:PSGSuite.CustomerId, [Parameter()] [String] $AlternateEmail, [Parameter()] [String] $CustomerDomain, [Parameter()] [String] $Language, [Parameter()] [String] $PhoneNumber, [Parameter()] [Google.Apis.Admin.Directory.directory_v1.Data.CustomerPostalAddress] $PostalAddress ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.customer' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.Customer' foreach ($key in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) { $body.$key = $PSBoundParameters[$key] } Write-Verbose "Updating Customer '$CustomerKey'" $request = $service.Customers.Patch($body,$CustomerKey) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Update-GSCustomer' function Get-GSDataTransfer { <# .SYNOPSIS Gets the list of Data Transfers .DESCRIPTION Gets the list of Data Transfers .PARAMETER DataTransferId The Id of the Data Transfer you would like to return info for specifically. Exclude to return the full list .PARAMETER Status Status of the transfer. .PARAMETER NewOwnerUserId Destination user's profile ID. .PARAMETER OldOwnerUserId Source user's profile ID. .PARAMETER CustomerId Immutable ID of the G Suite account. .PARAMETER PageSize PageSize of the result set. Defaults to 500 (although it's typically a much smaller number for most Customers) .PARAMETER Limit The maximum amount of results you want returned. Exclude or set to 0 to return all results .EXAMPLE Get-GSDataTransfer Gets the list of current Data Transfers #> [OutputType('Google.Apis.Admin.DataTransfer.datatransfer_v1.Data.ApplicationDataTransfer')] [cmdletbinding(DefaultParameterSetName = 'List')] Param ( [parameter(Mandatory,Position = 0,ParameterSetName = 'Get')] [String[]] $DataTransferId, [parameter(ParameterSetName = 'List')] [String] $Status, [parameter(ParameterSetName = 'List')] [String] $NewOwnerUserId, [parameter(ParameterSetName = 'List')] [String] $OldOwnerUserId, [parameter(ParameterSetName = 'List')] [String] $CustomerId = $Script:PSGSuite.CustomerID, [parameter(ParameterSetName = 'List')] [ValidateRange(1,500)] [Int] $PageSize = 500, [parameter(ParameterSetName = 'List')] [Alias('First')] [Int] $Limit = 0 ) Process { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.datatransfer' ServiceType = 'Google.Apis.Admin.DataTransfer.datatransfer_v1.DataTransferService' } $service = New-GoogleService @serviceParams try { if ($PSCmdlet.ParameterSetName -eq 'Get') { foreach ($I in $DataTransferId) { $request = $service.Transfers.Get($I) $request.Execute() } } else { $request = $service.Transfers.List() $request.CustomerId = $CustomerId 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) $PageSize = $Limit } $request.MaxResults = $PageSize foreach ($prop in @('NewOwnerUserId','OldOwnerUserId','Status')) { if ($PSBoundParameters.ContainsKey($prop)) { $request.$prop = $PSBoundParameters[$prop] } } Write-Verbose "Getting Data Transfer list" $response = @() [int]$i = 1 $overLimit = $false do { $result = $request.Execute() $response += $result.DataTransfers if ($result.NextPageToken) { $request.PageToken = $result.NextPageToken } [int]$retrieved = ($i + $result.DataTransfers.Count) - 1 Write-Verbose "Retrieved $retrieved Data Transfers..." if ($Limit -gt 0 -and $retrieved -eq $Limit) { Write-Verbose "Limit reached: $Limit" $overLimit = $true } elseif ($Limit -gt 0 -and ($retrieved + $PageSize) -gt $Limit) { $newPS = $Limit - $retrieved Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize,$newPS) $request.MaxResults = $newPS } [int]$i = $i + $result.DataTransfers.Count } until ($overLimit -or !$result.NextPageToken) return $response } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSDataTransfer' function Get-GSDataTransferApplication { <# .SYNOPSIS Gets the list of available Data Transfer Applications and their parameters .DESCRIPTION Gets the list of available Data Transfer Applications and their parameters .PARAMETER ApplicationId The Application Id of the Data Transfer Application you would like to return info for specifically. Exclude to return the full list .PARAMETER PageSize PageSize of the result set. Defaults to 500 (although it's typically a much smaller number for most Customers) .PARAMETER Limit The maximum amount of results you want returned. Exclude or set to 0 to return all results .EXAMPLE Get-GSDataTransferApplication Gets the list of available Data Transfer Applications #> [OutputType('Google.Apis.Admin.DataTransfer.datatransfer_v1.Data.Application')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0)] [String[]] $ApplicationId, [parameter(Mandatory = $false)] [ValidateRange(1,500)] [Int] $PageSize = 500, [parameter(Mandatory = $false)] [Alias('First')] [Int] $Limit = 0 ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.datatransfer' ServiceType = 'Google.Apis.Admin.DataTransfer.datatransfer_v1.DataTransferService' } $service = New-GoogleService @serviceParams } Process { try { if ($ApplicationId) { foreach ($I in $ApplicationId) { $request = $service.Applications.Get($I) $request.Execute() } } else { $request = $service.Applications.List() $request.CustomerId = $Script:PSGSuite.CustomerID 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) $PageSize = $Limit } $request.MaxResults = $PageSize Write-Verbose "Getting all Data Transfer Applications" $response = @() [int]$i = 1 $overLimit = $false do { $result = $request.Execute() $response += $result.Applications if ($result.NextPageToken) { $request.PageToken = $result.NextPageToken } [int]$retrieved = ($i + $result.Applications.Count) - 1 Write-Verbose "Retrieved $retrieved Data Transfer Applications..." if ($Limit -gt 0 -and $retrieved -eq $Limit) { Write-Verbose "Limit reached: $Limit" $overLimit = $true } elseif ($Limit -gt 0 -and ($retrieved + $PageSize) -gt $Limit) { $newPS = $Limit - $retrieved Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize,$newPS) $request.MaxResults = $newPS } [int]$i = $i + $result.Applications.Count } until ($overLimit -or !$result.NextPageToken) return $response } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSDataTransferApplication' function Start-GSDataTransfer { <# .SYNOPSIS Starts a Data Transfer from one user to another .DESCRIPTION Starts a Data Transfer from one user to another .PARAMETER OldOwnerUserId The email or unique Id of the owner you are transferring data *FROM* .PARAMETER NewOwnerUserId The email or unique Id of the owner you are transferring data *TO* .PARAMETER ApplicationId The application Id that you would like to transfer data for .PARAMETER PrivacyLevel The privacy level for the data you'd like to transfer *Valid for Drive & Docs data transfers only.* Available values are: * "SHARED": all shared content owned by the user * "PRIVATE": all private (unshared) content owned by the user .PARAMETER ReleaseResources If true, releases Calendar resources booked by the OldOwner to the NewOwner. *Valid for Calendar data transfers only.* .EXAMPLE Start-GSDataTransfer -OldOwnerUserId joe -NewOwnerUserId mark -ApplicationId 55656082996 -PrivacyLevel SHARED,PRIVATE Transfers all of Joe's data to Mark #> [OutputType('Google.Apis.Admin.DataTransfer.datatransfer_v1.Data.DataTransfer')] [cmdletbinding()] Param ( [parameter(Mandatory=$true,Position=0)] [string] $OldOwnerUserId, [parameter(Mandatory=$true,Position=1)] [string] $NewOwnerUserId, [parameter(Mandatory=$true,Position=2,ValueFromPipelineByPropertyName=$true)] [alias("id")] [string] $ApplicationId, [parameter(Mandatory=$false)] [ValidateSet("SHARED","PRIVATE")] [string[]] $PrivacyLevel, [parameter(Mandatory=$false)] [switch] $ReleaseResources ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.datatransfer' ServiceType = 'Google.Apis.Admin.DataTransfer.datatransfer_v1.DataTransferService' } $service = New-GoogleService @serviceParams $OldOwnerUserId = try { [bigint]$OldOwnerUserId } catch { Write-Verbose "Resolving Old Owner's UserId from '$OldOwnerUserId'" [bigint](Get-GSUser -User $OldOwnerUserId -Projection Basic -Verbose:$false | Select-Object -ExpandProperty id) } $NewOwnerUserId = try { [bigint]$NewOwnerUserId } catch { Write-Verbose "Resolving New Owner's UserId from '$NewOwnerUserId'" [bigint](Get-GSUser -User $NewOwnerUserId -Projection Basic -Verbose:$false | Select-Object -ExpandProperty id) } } Process { try { $body = New-Object 'Google.Apis.Admin.DataTransfer.datatransfer_v1.Data.DataTransfer' -Property @{ OldOwnerUserId = $OldOwnerUserId NewOwnerUserId = $NewOwnerUserId } $AppDataTransfers = New-Object 'Google.Apis.Admin.DataTransfer.datatransfer_v1.Data.ApplicationDataTransfer' -Property @{ ApplicationId = $ApplicationId } if ($PrivacyLevel) { $AppDataTransfers.ApplicationTransferParams = [Google.Apis.Admin.DataTransfer.datatransfer_v1.Data.ApplicationTransferParam[]](New-Object 'Google.Apis.Admin.DataTransfer.datatransfer_v1.Data.ApplicationTransferParam' -Property @{ Key = 'PRIVACY_LEVEL' Value = [String[]]$PrivacyLevel }) } elseif ($ReleaseResources) { $AppDataTransfers.ApplicationTransferParams = [Google.Apis.Admin.DataTransfer.datatransfer_v1.Data.ApplicationTransferParam[]](New-Object 'Google.Apis.Admin.DataTransfer.datatransfer_v1.Data.ApplicationTransferParam' -Property @{ Key = 'RELEASE_RESOURCES' Value = [String[]]'TRUE' }) } $body.ApplicationDataTransfers = [Google.Apis.Admin.DataTransfer.datatransfer_v1.Data.ApplicationDataTransfer[]]$AppDataTransfers $request = $service.Transfers.Insert($body) Write-Verbose "Starting Data Transfer from User Id '$OldOwnerUserId' to User Id '$NewOwnerUserId'" $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Start-GSDataTransfer' function Add-GSGmailDelegate { <# .SYNOPSIS Adds a delegate with its verification status set directly to accepted, without sending any verification email. The delegate user must be a member of the same G Suite organization as the delegator user. .DESCRIPTION Adds a delegate with its verification status set directly to accepted, without sending any verification email. The delegate user must be a member of the same G Suite organization as the delegator user. Gmail imposes limtations on the number of delegates and delegators each user in a G Suite organization can have. These limits depend on your organization, but in general each user can have up to 25 delegates and up to 10 delegators. Note that a delegate user must be referred to by their primary email address, and not an email alias. Also note that when a new delegate is created, there may be up to a one minute delay before the new delegate is available for use. .PARAMETER User User's email address to delegate access to. .PARAMETER Delegate Delegate's email address to receive delegate access. .EXAMPLE Add-GSGmailDelegate -User tony@domain.com -Delegate peter@domain.com Provide Peter delegate access to Tony's inbox. #> [OutputType('Google.Apis.Gmail.v1.Data.Delegate')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0)] [Alias("From","Delegator")] [ValidateNotNullOrEmpty()] [String] $User, [parameter(Mandatory = $true,Position = 1)] [Alias("To")] [ValidateNotNullOrEmpty()] [String] $Delegate ) Begin { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } if ($Delegate -notlike "*@*.*") { $Delegate = "$($Delegate)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/gmail.settings.sharing' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams } Process { try { Write-Verbose "Adding delegate access to user '$User's inbox for delegate '$Delegate'" $body = New-Object 'Google.Apis.Gmail.v1.Data.Delegate' -Property @{ DelegateEmail = $Delegate } $request = $service.Users.Settings.Delegates.Create($body,$User) $request.Execute() } catch { $origError = $_ if ($group = Get-GSGroup -Group $User -Verbose:$false -ErrorAction SilentlyContinue) { Write-Warning "$User is a group email, not a user account. You can only manage delegate access for a user's inbox. Please add $Delegate to the group $User instead." } elseif ($group = Get-GSGroup -Group $Delegate -Verbose:$false -ErrorAction SilentlyContinue) { Write-Warning "$Delegate is a group email, not a user account. You can only delegate access to other users." } else { $dele = Get-GSGmailDelegates -User $User -NoGroupCheck -ErrorAction SilentlyContinue -Verbose:$false if ($dele.DelegateEmail -contains $Delegate -and $dele.VerificationStatus -eq 'accepted') { Write-Warning "'$Delegate' already has delegate access to user '$User's inbox. No action needed." } elseif ($dele.DelegateEmail -contains $Delegate -and $dele.VerificationStatus -ne 'accepted') { Write-Warning "$Delegate was already invited for delegated access to user '$User's inbox, but VerificationStatus is currently '$($dele.VerificationStatus)'" } else { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($origError) } else { Write-Error $origError } } } } } } Export-ModuleMember -Function 'Add-GSGmailDelegate' function Get-GSGmailDelegate { <# .SYNOPSIS Gets delegates for the specified account. .DESCRIPTION Gets delegates for the specified account. .PARAMETER User User's email to get delegates for. .PARAMETER Delegate The specific delegate to get. If excluded returns the list of delegates for the user. .PARAMETER NoGroupCheck By default, this will check if the User email is a group email which cannot be delegated if the attempt to delegate access fails. Include this switch to prevent the group check and return the original error. .EXAMPLE Get-GSGmailDelegate -User tony@domain.com Gets the list of users who have delegate access to Tony's inbox. #> [OutputType('Google.Apis.Gmail.v1.Data.Delegate')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0)] [Alias("From","Delegator")] [ValidateNotNullOrEmpty()] [String[]] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false,Position = 1)] [Alias("To")] [ValidateNotNullOrEmpty()] [String] $Delegate, [parameter(Mandatory = $false)] [switch] $NoGroupCheck ) Process { foreach ($U in $User) { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/gmail.settings.basic' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $U } $service = New-GoogleService @serviceParams if ($PSBoundParameters.Keys -contains 'Delegate') { try { Write-Verbose "Getting Gmail Delegate '$Delegate' for user '$U'" $request = $service.Users.Settings.Delegates.Get($U,$Delegate) $request.Execute() } catch { $origError = $_ if (!$NoGroupCheck -and ($group = Get-GSGroup -Group $U -Verbose:$false -ErrorAction SilentlyContinue)) { Write-Warning "$U is a group, not a user. You can only manage delegates for a user." } else { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($origError) } else { Write-Error $origError } } } } else { try { Write-Verbose "Getting Gmail Delegate list for user '$U'" $request = $service.Users.Settings.Delegates.List($U) $res = $request.Execute() if ($res.Delegates) { $res.Delegates | Add-Member -MemberType NoteProperty -Name Delegator -Value $U -Force -PassThru } else { Write-Warning "No delegates found for user '$U'" } } catch { $origError = $_ if (!$NoGroupCheck -and ($group = Get-GSGroup -Group $U -Verbose:$false -ErrorAction SilentlyContinue)) { Write-Warning "$U is a group, not a user. You can only manage delegates for a user." } else { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($origError) } else { Write-Error $origError } } } } } } } Export-ModuleMember -Function 'Get-GSGmailDelegate' function Remove-GSGmailDelegate { <# .SYNOPSIS Removes the specified delegate (which can be of any verification status), and revokes any verification that may have been required for using it. .DESCRIPTION Removes the specified delegate (which can be of any verification status), and revokes any verification that may have been required for using it. Note that a delegate user must be referred to by their primary email address, and not an email alias. .PARAMETER User User's email address to remove delegate access to .PARAMETER Delegate Delegate's email address to remove .EXAMPLE Remove-GSGmailDelegate -User tony@domain.com -Delegate peter@domain.com Removes Peter's access to Tony's inbox. #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $true,Position = 0)] [Alias("From","Delegator")] [ValidateNotNullOrEmpty()] [String] $User, [parameter(Mandatory = $true,Position = 1)] [Alias("To")] [ValidateNotNullOrEmpty()] [String] $Delegate ) Begin { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } if ($Delegate -notlike "*@*.*") { $Delegate = "$($Delegate)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/gmail.settings.sharing' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams } Process { if ($PSCmdlet.ShouldProcess("Removing delegate access for '$Delegate' from user '$User's inbox")) { try { Write-Verbose "Removing delegate access for '$Delegate' from user '$User's inbox" $request = $service.Users.Settings.Delegates.Delete($User,$Delegate) $request.Execute() Write-Verbose "Successfully removed delegate access for user '$User's inbox for delegate '$Delegate'" } catch { $origError = $_ if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($origError) } else { Write-Error $origError } } } } } Export-ModuleMember -Function 'Remove-GSGmailDelegate' function Get-GSDomain { <# .SYNOPSIS Retrieves a Domain .DESCRIPTION Retrieves a Domain .PARAMETER DomainName Name of the domain to retrieve. If excluded, returns the list of domains. .EXAMPLE Get-GSDDomain Returns the list of domains. #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Domains')] [CmdletBinding()] Param( [Parameter(Position = 0,ValueFromPipeline,ValueFromPipelineByPropertyName)] [Alias('Domain')] [String[]] $DomainName ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.domain' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { if ($PSBoundParameters.ContainsKey('DomainName')) { foreach ($domain in $DomainName) { try { Write-Verbose "Getting Domain '$domain'" $request = $service.Domains.Get($Script:PSGSuite.CustomerId,$domain) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } else { try { Write-Verbose "Getting the list of Domains" $request = $service.Domains.List($Script:PSGSuite.CustomerId) $request.Execute().Domains } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Get-GSDomain' function Get-GSDomainAlias { <# .SYNOPSIS Retrieves a Domain Alias .DESCRIPTION Retrieves a Domain Alias .PARAMETER DomainAliasName Name of the domain alias to retrieve. If excluded, returns the list of domain aliases. .PARAMETER ParentDomainName Name of the parent domain to list aliases for. If excluded, lists all aliases for all domains. .EXAMPLE Get-GSDDomainAlias Returns the list of domain aliases for all domains. #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.DomainAlias')] [CmdletBinding(DefaultParameterSetName = "List")] Param( [Parameter(Position = 0,ValueFromPipeline,ValueFromPipelineByPropertyName,ParameterSetName = "Get")] [Alias('DomainAlias')] [String[]] $DomainAliasName, [Parameter(Position = 0,ValueFromPipeline,ValueFromPipelineByPropertyName,ParameterSetName = "List")] [String[]] $ParentDomainName ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.domain' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { if ($PSBoundParameters.ContainsKey('DomainAliasName')) { foreach ($alias in $DomainAliasName) { try { Write-Verbose "Getting DomainAlias '$alias'" $request = $service.DomainAliases.Get($Script:PSGSuite.CustomerId,$alias) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } else { try { $request = $service.DomainAliases.List($Script:PSGSuite.CustomerId) if ($PSBoundParameters.ContainsKey('ParentDomainName')) { foreach ($pDom in $ParentDomainName) { Write-Verbose "Getting the list of all DomainAliases under parent domain '$pDom'" $request.ParentDomainName = $pDom $request.Execute().DomainAliasesValue } } else { Write-Verbose "Getting the list of all DomainAliases" $request.Execute().DomainAliasesValue } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Get-GSDomainAlias' function New-GSDomain { <# .SYNOPSIS Adds a new Domain .DESCRIPTION Adds a new Domain .PARAMETER DomainName Name of the domain to add. .EXAMPLE New-GSDDomain -DomainName 'testing.com' Adds a new domain named 'testing.com' #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Domains')] [CmdletBinding()] Param( [Parameter(Position = 0,ValueFromPipeline,ValueFromPipelineByPropertyName)] [Alias('Domain')] [String] $DomainName ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.domain' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { Write-Verbose "Adding Domain '$DomainName'" $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.Domains' -Property @{ DomainName = $DomainName } $request = $service.Domains.Insert($body,$Script:PSGSuite.CustomerId) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'New-GSDomain' function New-GSDomainAlias { <# .SYNOPSIS Adds a new Domain Alias .DESCRIPTION Adds a new Domain Alias .PARAMETER DomainAliasName Name of the domain alias to add. .PARAMETER ParentDomainName Name of the parent domain to add the alias for. .EXAMPLE New-GSDDomainAlias -DomainAliasName 'testingalias.com' -ParentDomainName 'testing.com' Adds a new domain alias named 'testingalias.com' to parent domain 'testing.com' #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.DomainAlias')] [CmdletBinding()] Param( [Parameter(Mandatory,Position = 0,ValueFromPipeline,ValueFromPipelineByPropertyName)] [Alias('DomainAlias')] [String] $DomainAliasName, [Parameter(Mandatory,Position = 1,ValueFromPipeline,ValueFromPipelineByPropertyName)] [String] $ParentDomainName ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.domain' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { Write-Verbose "Adding DomainAlias '$DomainAliasName' to domain '$ParentDomainName'" $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.DomainAlias' -Property @{ DomainAliasName = $DomainAliasName ParentDomainName = $ParentDomainName } $request = $service.DomainAliases.Insert($body,$Script:PSGSuite.CustomerId) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'New-GSDomainAlias' function Remove-GSDomain { <# .SYNOPSIS Removes a Domain .DESCRIPTION Removes a Domain .PARAMETER DomainName Name of the domain to remove. .EXAMPLE Remove-GSDDomain 'testing.com' Removes the 'testing.com' domain from your account. #> [CmdletBinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param( [Parameter(Position = 0,ValueFromPipeline,ValueFromPipelineByPropertyName)] [Alias('Domain')] [String[]] $DomainName ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.domain' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { foreach ($domain in $DomainName) { try { if ($PSCmdlet.ShouldProcess("Removing Domain '$domain'")) { Write-Verbose "Removing Domain '$domain'" $request = $service.Domains.Get($Script:PSGSuite.CustomerId,$domain) $request.Execute() Write-Verbose "Domain '$domain' removed successfully" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Remove-GSDomain' function Remove-GSDomainAlias { <# .SYNOPSIS Removes a Domain Alias .DESCRIPTION Removes a Domain Alias .PARAMETER DomainAliasName Alias of the domain to remove. .EXAMPLE Remove-GSDDomainAlias 'testingalias.com' Removes the 'testingalias.com' domain alias from your account. #> [CmdletBinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param( [Parameter(Position = 0,ValueFromPipeline,ValueFromPipelineByPropertyName)] [Alias('DomainAlias')] [String[]] $DomainAliasName ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.domain' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { foreach ($alias in $DomainAliasName) { try { if ($PSCmdlet.ShouldProcess("Removing Domain Alias '$domain'")) { Write-Verbose "Removing Domain Alias '$alias'" $request = $service.DomainAliases.Delete($Script:PSGSuite.CustomerId,$alias) $request.Execute() Write-Verbose "Domain Alias '$alias' removed successfully" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Remove-GSDomainAlias' function Add-GSDocContent { <# .SYNOPSIS Adds content to a Google Doc via appending new text. This does not overwrite existing content .DESCRIPTION Adds content to a Google Doc via appending new text. This does not overwrite existing content .PARAMETER FileID The unique Id of the file to add content to .PARAMETER Value The content to add .PARAMETER User The email or unique Id of the owner of the Drive file Defaults to the AdminEmail user .EXAMPLE $newLogStrings | Add-GSDocContent -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976' Appends the strings in the $newLogStrings variable to the existing the content of the specified Google Doc. #> [CmdLetBinding()] Param ( [parameter(Mandatory = $true,Position = 0)] [String] $FileID, [parameter(Mandatory = $true,ValueFromPipeline = $true)] [String[]] $Value, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail ) Begin { $service = New-GoogleService @serviceParams $stream = New-Object 'System.IO.MemoryStream' $writer = New-Object 'System.IO.StreamWriter' $stream $currentContent = Get-GSDocContent -FileID $FileID -User $User -Verbose:$false $concatStrings = @($currentContent) } 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.Drive.v3.DriveService' User = $User } foreach ($string in $Value) { $concatStrings += $string } } End { try { $concatStrings = $concatStrings -join "`n" $writer.Write($concatStrings) $writer.Flush() $contentType = 'text/plain' $body = New-Object 'Google.Apis.Drive.v3.Data.File' $request = $service.Files.Update($body,$FileId,$stream,$contentType) $request.QuotaUser = $User $request.ChunkSize = 512KB $request.SupportsAllDrives = $true Write-Verbose "Adding content to File '$FileID'" $request.Upload() | Out-Null } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } finally { if ($stream) { $stream.Close() } } } } Export-ModuleMember -Function 'Add-GSDocContent' function Add-GSDrivePermission { <# .SYNOPSIS Adds a new permission to a Drive file .DESCRIPTION Adds a new permission to a Drive file .PARAMETER User The owner of the Drive file Defaults to the AdminEmail user .PARAMETER FileId The unique Id of the Drive file you would like to add the permission to .PARAMETER Role The role/permission set you would like to give the email $EmailAddress Available values are: * "Owner" * "Writer" * "Commenter" * "Reader" * "Organizer" * "FileOrganizer" .PARAMETER Type The type of the grantee Available values are: * "User": a user email * "Group": a group email * "Domain": the entire domain * "Anyone": public access .PARAMETER EmailAddress The email address of the user or group to which this permission refers .PARAMETER Domain The domain to which this permission refers .PARAMETER ExpirationTime The time at which this permission will expire. Expiration times have the following restrictions: * They can only be set on user and group permissions * The time must be in the future * The time cannot be more than a year in the future .PARAMETER EmailMessage A plain text custom message to include in the notification email .PARAMETER SendNotificationEmail Whether to send a notification email when sharing to users or groups. This defaults to **FALSE** for users and groups in PSGSuite, and is not allowed for other requests. **It must not be disabled for ownership transfers** .PARAMETER AllowFileDiscovery Whether the permission allows the file to be discovered through search. This is only applicable for permissions of type domain or anyone .PARAMETER TransferOwnership Confirms transfer of ownership if the Role is set to 'Owner'. You can also force the same behavior by passing -Confirm:$false instead .PARAMETER UseDomainAdminAccess Whether the request should be treated as if it was issued by a domain administrator; if set to true, then the requester will be granted access if they are an administrator of the domain to which the item belongs .EXAMPLE Add-GSDrivePermission -FileId "1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976" -Role Owner -Type User -EmailAddress joe -SendNotificationEmail -Confirm:$false Adds user joe@domain.com as the new owner of the file Id and sets the AdminEmail user as a Writer on the file #> [OutputType('Google.Apis.Drive.v3.Data.Permission')] [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High",DefaultParameterSetName = "Email")] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias('Id')] [String] $FileId, [parameter(Mandatory = $true)] [ValidateSet("Owner","Writer","Commenter","Reader","Organizer","FileOrganizer")] [String] $Role, [parameter(Mandatory = $true)] [ValidateSet("User","Group","Domain","Anyone")] [String] $Type, [parameter(Mandatory = $false,Position = 1,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false,ParameterSetName = "Email")] [String] $EmailAddress, [parameter(Mandatory = $false,ParameterSetName = "Domain")] [String] $Domain, [parameter(Mandatory = $false)] [DateTime] $ExpirationTime, [parameter(Mandatory = $false)] [string] $EmailMessage, [parameter(Mandatory = $false)] [Switch] $SendNotificationEmail, [parameter(Mandatory = $false)] [Switch] $AllowFileDiscovery, [parameter(Mandatory = $false)] [Alias('ConfirmTransferOfOwnership','TransferOfOwnership')] [switch] $TransferOwnership, [parameter(Mandatory = $false)] [switch] $UseDomainAdminAccess ) 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.Drive.v3.DriveService' User = $User } $service = New-GoogleService @serviceParams try { if ($Role -eq "Owner" -and !$TransferOwnership) { if ($PSCmdlet.ShouldProcess("Confirm transfer of ownership of FileId '$FileID' from user '$User' to user '$EmailAddress'")) { $PSBoundParameters['TransferOwnership'] = $true $TransferOwnership = $true } else { throw "The TransferOwnership parameter is required when setting the 'Owner' role." } } if (($Type -eq "User" -or $Type -eq "Group") -and !$EmailAddress) { throw "The EmailAddress parameter is required for types 'User' or 'Group'." } if (($Type -eq "User" -or $Type -eq "Group") -and ($PSBoundParameters.Keys -contains 'AllowFileDiscovery')) { Write-Warning "The AllowFileDiscovery parameter is only applicable for types 'Domain' or 'Anyone' This parameter will be excluded from this request." $PSBoundParameters.Remove('AllowFileDiscovery') | Out-Null } if ($TransferOwnership -and !$SendNotificationEmail) { $PSBoundParameters['SendNotificationEmail'] = $true Write-Warning "Setting SendNotificationEmail to 'True' to prevent errors (required for Ownership transfers)" } $body = New-Object 'Google.Apis.Drive.v3.Data.Permission' foreach ($key in $PSBoundParameters.Keys) { switch ($key) { EmailAddress { if ($EmailAddress -ceq 'me') { $EmailAddress = $Script:PSGSuite.AdminEmail } elseif ($EmailAddress -notlike "*@*.*") { $EmailAddress = "$($EmailAddress)@$($Script:PSGSuite.Domain)" } $body.EmailAddress = $EmailAddress } Role { $body.$key = ($PSBoundParameters[$key]).ToLower().Replace("fileorganizer","fileOrganizer") } Type { $body.$key = ($PSBoundParameters[$key]).ToLower() } Default { if ($body.PSObject.Properties.Name -contains $key) { $body.$key = $PSBoundParameters[$key] } } } } $request = $service.Permissions.Create($body,$FileId) $request.SupportsAllDrives = $true foreach ($key in $PSBoundParameters.Keys) { if ($request.PSObject.Properties.Name -contains $key -and $key -ne 'FileId') { $request.$key = $PSBoundParameters[$key] } } if ($PSBoundParameters.Keys -notcontains 'SendNotificationEmail') { $request.SendNotificationEmail = $false } Write-Verbose "Adding Drive Permission of '$Role' for user '$EmailAddress' on Id '$FileID'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Add-GSDrivePermission' function Copy-GSDriveFile { <# .SYNOPSIS Make a copy of a file in Drive .DESCRIPTION Make a copy of a file in Drive .PARAMETER FileID The unique Id of the file to copy .PARAMETER User The email or unique Id of the owner of the Drive file Defaults to the AdminEmail user .PARAMETER Name The name of the new Drive file copy .PARAMETER Description The description of the new Drive file copy .PARAMETER Parents The parent Ids of the new Drive file copy .PARAMETER Projection The defined subset of fields to be returned Available values are: * "Minimal" * "Standard" * "Full" * "Access" .PARAMETER Fields The specific fields to returned .EXAMPLE Copy-GSDriveFile -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976' -Name "New Daily Checklist" Copies the Drive file Id to a new Drive file named 'New Daily Checklist' #> [OutputType('Google.Apis.Drive.v3.Data.File')] [cmdletbinding(DefaultParameterSetName = "Depth")] Param ( [parameter(Mandatory = $true,Position = 0)] [String] $FileID, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false)] [String] $Name, [parameter(Mandatory = $false)] [String] $Description, [parameter(Mandatory = $false)] [String[]] $Parents, [parameter(Mandatory = $false,ParameterSetName = "Depth")] [Alias('Depth')] [ValidateSet("Minimal","Standard","Full","Access")] [String] $Projection = "Full", [parameter(Mandatory = $false,ParameterSetName = "Fields")] [ValidateSet("appProperties","capabilities","contentHints","createdTime","description","explicitlyTrashed","fileExtension","folderColorRgb","fullFileExtension","hasThumbnail","headRevisionId","iconLink","id","imageMediaMetadata","isAppAuthorized","kind","lastModifyingUser","md5Checksum","mimeType","modifiedByMe","modifiedByMeTime","modifiedTime","name","originalFilename","ownedByMe","owners","parents","permissions","properties","quotaBytesUsed","shared","sharedWithMeTime","sharingUser","size","spaces","starred","thumbnailLink","thumbnailVersion","trashed","version","videoMediaMetadata","viewedByMe","viewedByMeTime","viewersCanCopyContent","webContentLink","webViewLink","writersCanShare")] [String[]] $Fields ) Begin { if ($Projection) { $fs = switch ($Projection) { Standard { @("createdTime","description","fileExtension","id","lastModifyingUser","modifiedTime","name","owners","parents","properties","version","webContentLink","webViewLink") } Access { @("createdTime","description","fileExtension","id","lastModifyingUser","modifiedTime","name","ownedByMe","owners","parents","permissionIds","permissions","shared","sharedWithMeTime","sharingUser","viewedByMe","viewedByMeTime","viewersCanCopyContent","writersCanShare") } Full { @("appProperties","capabilities","contentHints","createdTime","description","explicitlyTrashed","fileExtension","folderColorRgb","fullFileExtension","hasAugmentedPermissions","hasThumbnail","headRevisionId","iconLink","id","imageMediaMetadata","isAppAuthorized","kind","lastModifyingUser","md5Checksum","mimeType","modifiedByMe","modifiedByMeTime","modifiedTime","name","originalFilename","ownedByMe","owners","parents","permissionIds","permissions","properties","quotaBytesUsed","shared","sharedWithMeTime","sharingUser","size","spaces","starred","teamDriveId","thumbnailLink","thumbnailVersion","trashed","trashedTime","trashingUser","version","videoMediaMetadata","viewedByMe","viewedByMeTime","viewersCanCopyContent","webContentLink","webViewLink","writersCanShare") } } } elseif ($PSBoundParameters.ContainsKey('Fields')) { $fs = $Fields } } 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.Drive.v3.DriveService' User = $User } $service = New-GoogleService @serviceParams try { $body = New-Object 'Google.Apis.Drive.v3.Data.File' if ($Name) { $body.Name = $Name } if ($Description) { $body.Description = $Description } if ($Parents) { $body.Parents = [String[]]$Parents } $request = $service.Files.Copy($body,$FileID) $request.SupportsAllDrives = $true if ($fs) { $request.Fields = "$($fs -join ",")" } Write-Verbose "Copying drive file id '$FileID'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Copy-GSDriveFile' function Export-GSDriveFile { <# .SYNOPSIS Exports a Drive file as if you chose "Export" from the File menu when viewing the file .DESCRIPTION Exports a Drive file as if you chose "Export" from the File menu when viewing the file .PARAMETER FileID The unique Id of the file to export .PARAMETER User The email or unique Id of the owner of the Drive file Defaults to the AdminEmail user .PARAMETER Type The type of local file you would like to export the Drive file as Available values are: * "CSV" * "HTML" * "JPEG" * "JSON" * "MSExcel" * "MSPowerPoint" * "MSWordDoc" * "OpenOfficeDoc" * "OpenOfficeSheet" * "PDF" * "PlainText" * "PNG" * "RichText" * "SVG" .PARAMETER OutFilePath The directory path that you would like to export the Drive file to Defaults to the current working directory .PARAMETER Projection The defined subset of fields to be returned Available values are: * "Minimal" * "Standard" * "Full" * "Access" .PARAMETER Fields The specific fields to returned .PARAMETER Force If $true, overwrites any existing files at the same path. .EXAMPLE Export-GSDriveFile -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976' -Type CSV -OutFilePath .\SheetExport.csv Exports the Drive file as a CSV to the current working directory #> [CmdLetBinding(DefaultParameterSetName = "Depth")] Param ( [parameter(Mandatory = $true,Position = 0)] [String] $FileID, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $true)] [ValidateSet("CSV","EPUB","HTML","HTMLZipped","JPEG","JSON","MSExcel","MSPowerPoint","MSWordDoc","OpenOfficeDoc","OpenOfficePresentation","OpenOfficeSheet","PDF","PlainText","PNG","RichText","SVG","TSV")] [String] $Type, [parameter(Mandatory = $false)] [String] $OutFilePath, [parameter(Mandatory = $false,ParameterSetName = "Depth")] [Alias('Depth')] [ValidateSet("Minimal","Standard","Full","Access")] [String] $Projection = "Full", [parameter(Mandatory = $false,ParameterSetName = "Fields")] [ValidateSet("appProperties","capabilities","contentHints","createdTime","description","explicitlyTrashed","fileExtension","folderColorRgb","fullFileExtension","hasThumbnail","headRevisionId","iconLink","id","imageMediaMetadata","isAppAuthorized","kind","lastModifyingUser","md5Checksum","mimeType","modifiedByMe","modifiedByMeTime","modifiedTime","name","originalFilename","ownedByMe","owners","parents","permissions","properties","quotaBytesUsed","shared","sharedWithMeTime","sharingUser","size","spaces","starred","thumbnailLink","thumbnailVersion","trashed","version","videoMediaMetadata","viewedByMe","viewedByMeTime","viewersCanCopyContent","webContentLink","webViewLink","writersCanShare")] [String[]] $Fields, [parameter(Mandatory = $false)] [Switch] $Force ) Begin { if ($Projection) { $fs = switch ($Projection) { Standard { @("createdTime","description","fileExtension","id","lastModifyingUser","modifiedTime","name","owners","parents","properties","version","webContentLink","webViewLink") } Access { @("createdTime","description","fileExtension","id","lastModifyingUser","modifiedTime","name","ownedByMe","owners","parents","permissionIds","permissions","shared","sharedWithMeTime","sharingUser","viewedByMe","viewedByMeTime","viewersCanCopyContent","writersCanShare") } Full { @("appProperties","capabilities","contentHints","createdTime","description","explicitlyTrashed","fileExtension","folderColorRgb","fullFileExtension","hasAugmentedPermissions","hasThumbnail","headRevisionId","iconLink","id","imageMediaMetadata","isAppAuthorized","kind","lastModifyingUser","md5Checksum","mimeType","modifiedByMe","modifiedByMeTime","modifiedTime","name","originalFilename","ownedByMe","owners","parents","permissionIds","permissions","properties","quotaBytesUsed","shared","sharedWithMeTime","sharingUser","size","spaces","starred","teamDriveId","thumbnailLink","thumbnailVersion","trashed","trashedTime","trashingUser","version","videoMediaMetadata","viewedByMe","viewedByMeTime","viewersCanCopyContent","webContentLink","webViewLink","writersCanShare") } } } elseif ($PSBoundParameters.ContainsKey('Fields')) { $fs = $Fields } $mimeHash = @{ CSV = "text/csv" EPUB = "application/epub+zip" HTML = "text/html" HTMLZipped = "application/zip" JPEG = "image/jpeg" JSON = "application/vnd.google-apps.script+json" MSExcel = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" MSPowerPoint = "application/vnd.openxmlformats-officedocument.presentationml.presentation" MSWordDoc = "application/vnd.openxmlformats-officedocument.wordprocessingml.document" OpenOfficeDoc = "application/vnd.oasis.opendocument.text" OpenOfficePresentation = "application/vnd.oasis.opendocument.presentation" OpenOfficeSheet = "application/x-vnd.oasis.opendocument.spreadsheet" PDF = "application/pdf" PlainText = "text/plain" PNG = "image/png" RichText = "application/rtf" SVG = "image/svg+xml" TSV = "text/tab-separated-values" } } 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.Drive.v3.DriveService' User = $User } $service = New-GoogleService @serviceParams try { $request = $service.Files.Export($FileID,($mimeHash[$Type])) if ($fs) { $request.Fields = $($fs -join ",") } if ($OutFilePath) { if ((Test-Path $OutFilePath) -and !$Force) { throw "File '$OutFilePath' already exists. If you would like to overwrite it, use the -Force parameter." } else { Write-Verbose "Saving file to path '$OutFilePath'" $stream = [System.IO.File]::Create($OutFilePath) $request.Download($stream) $stream.Close() } } else { Write-Verbose "Getting content of File '$FileID' as Type '$Type'" $request.Execute() } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Export-GSDriveFile' function Get-GSDocContent { <# .SYNOPSIS Gets the content of a Google Doc and returns it as an array of strings. Supports HTML or PlainText .DESCRIPTION Gets the content of a Google Doc and returns it as an array of strings. Supports HTML or PlainText .PARAMETER FileID The unique Id of the file to get content of .PARAMETER User The email or unique Id of the owner of the Drive file Defaults to the AdminEmail user .PARAMETER Type Whether to get the results in HTML or PlainText format. .EXAMPLE Get-GSDocContent -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976' Exports the Drive file as a CSV to the current working directory #> [CmdLetBinding()] Param ( [parameter(Mandatory = $true,Position = 0)] [String] $FileID, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false)] [ValidateSet("HTML","PlainText")] [String] $Type ) Begin { $typeParam = @{} if ($PSBoundParameters.Keys -notcontains 'Type') { $typeParam['Type'] = "PlainText" } } Process { try { (Export-GSDriveFile @PSBoundParameters -Projection Minimal @typeParam) -split "`n" Write-Verbose "Content retrieved for File '$FileID'" } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSDocContent' function Get-GSDrive { <# .SYNOPSIS Gets information about a Shared Drive .DESCRIPTION Gets information about a Shared Drive .PARAMETER DriveId The unique Id of the Shared Drive. If excluded, the list of Shared Drives will be returned .PARAMETER User The email or unique Id of the user with access to the Shared Drive .PARAMETER Filter Query string for searching Shared Drives. See the "Search for Files and Shared Drives" guide for the supported syntax: https://developers.google.com/drive/v3/web/search-parameters PowerShell filter syntax here is supported as "best effort". Please use Google's filter operators and syntax to ensure best results .PARAMETER UseDomainAdminAccess Issue the request as a domain administrator; if set to true, then all Shared Drives of the domain in which the requester is an administrator are returned. .PARAMETER PageSize The page size of the result set .PARAMETER Limit The maximum amount of results you want returned. Exclude or set to 0 to return all results .EXAMPLE Get-GSDrive -Limit 3 -UseDomainAdminAccess Gets the first 3 Shared Drives in the domain. #> [OutputType('Google.Apis.Drive.v3.Data.Drive')] [cmdletbinding(DefaultParameterSetName = "List")] Param ( [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true,ParameterSetName = "Get")] [Alias('Id','TeamDriveId')] [String[]] $DriveId, [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false,ParameterSetName = "List")] [Alias('Q','Query')] [String] $Filter, [parameter(Mandatory = $false,ParameterSetName = "List")] [Switch] $UseDomainAdminAccess, [parameter(Mandatory = $false,ParameterSetName = "List")] [ValidateRange(1,100)] [Int] $PageSize = "100", [parameter(Mandatory = $false,ParameterSetName = "List")] [Alias('First')] [Int] $Limit = 0 ) Process { if ($UseDomainAdminAccess -or $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.Drive.v3.DriveService' User = $User } $service = New-GoogleService @serviceParams try { switch ($PSCmdlet.ParameterSetName) { Get { foreach ($id in $DriveId) { $request = $service.Drives.Get($id) Write-Verbose "Getting Shared Drive '$id' for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } } List { $request = $service.Drives.List() 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) $PageSize = $Limit } $request.PageSize = $PageSize if ($UseDomainAdminAccess) { $request.UseDomainAdminAccess = $true Write-Verbose "Getting Shared Drives as Domain Admin" } else { Write-Verbose "Getting Shared Drives for user '$User'" } if ($Filter) { $FilterFmt = $Filter -replace " -eq ","=" -replace " -like "," contains " -replace " -match "," contains " -replace " -contains "," contains " -creplace "'True'","True" -creplace "'False'","False" -replace " -in "," in " -replace " -le ",'<=' -replace " -ge ",">=" -replace " -gt ",'>' -replace " -lt ",'<' -replace " -ne ","!=" -replace " -and "," and " -replace " -or "," or " -replace " -not "," not " $request.Q = $($FilterFmt -join " ") } [int]$i = 1 $overLimit = $false do { $result = $request.Execute() if ($result.Drives) { $result.Drives | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } $request.PageToken = $result.NextPageToken [int]$retrieved = ($i + $result.Drives.Count) - 1 Write-Verbose "Retrieved $retrieved Shared Drives..." if ($Limit -gt 0 -and $retrieved -eq $Limit) { Write-Verbose "Limit reached: $Limit" $overLimit = $true } elseif ($Limit -gt 0 -and ($retrieved + $PageSize) -gt $Limit) { $newPS = $Limit - $retrieved Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize,$newPS) $request.PageSize = $newPS } [int]$i = $i + $result.Drives.Count } until ($overLimit -or !$result.NextPageToken) } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSDrive' function Get-GSDriveFile { <# .SYNOPSIS Gets information about or downloads a Drive file .DESCRIPTION Gets information about or downloads a Drive file .PARAMETER FileId The unique Id of the file to get .PARAMETER User The email or unique Id of the owner of the Drive file Defaults to the AdminEmail user .PARAMETER OutFilePath The directory path that you would like to download the Drive file to. If excluded, only the Drive file information will be returned .PARAMETER Projection The defined subset of fields to be returned Available values are: * "Standard" * "Full" * "Access" .PARAMETER Fields The specific fields to returned .PARAMETER Force If $true and OutFilePath is specified, overwrites any existing files at the desired path. .EXAMPLE Get-GSDriveFile -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976' Gets the information for the file .EXAMPLE Get-GSDriveFile -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976' -OutFilePath (Get-Location).Path Gets the information for the file and saves the file in the current working directory #> [OutputType('Google.Apis.Drive.v3.Data.File')] [cmdletbinding(DefaultParameterSetName = "Depth")] Param ( [parameter(Mandatory = $true,Position = 0)] [String[]] $FileId, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false)] [Alias('SaveFileTo')] [String] $OutFilePath, [parameter(Mandatory = $false,ParameterSetName = "Depth")] [Alias('Depth')] [ValidateSet("Standard","Full","Access")] [String] $Projection = "Full", [parameter(Mandatory = $false,ParameterSetName = "Fields")] [ValidateSet("appProperties","capabilities","contentHints","createdTime","description","explicitlyTrashed","fileExtension","folderColorRgb","fullFileExtension","hasThumbnail","headRevisionId","iconLink","id","imageMediaMetadata","isAppAuthorized","kind","lastModifyingUser","md5Checksum","mimeType","modifiedByMe","modifiedByMeTime","modifiedTime","name","originalFilename","ownedByMe","owners","parents","permissions","properties","quotaBytesUsed","shared","sharedWithMeTime","sharingUser","size","spaces","starred","thumbnailLink","thumbnailVersion","trashed","version","videoMediaMetadata","viewedByMe","viewedByMeTime","viewersCanCopyContent","webContentLink","webViewLink","writersCanShare")] [String[]] $Fields, [parameter(Mandatory = $false)] [Switch] $Force ) Begin { if ($PSCmdlet.ParameterSetName -eq 'Depth') { $fs = switch ($Projection) { Standard { @("createdTime","description","fileExtension","id","lastModifyingUser","modifiedTime","name","owners","parents","properties","version","webContentLink","webViewLink") } Access { @("createdTime","description","fileExtension","id","lastModifyingUser","modifiedTime","name","ownedByMe","owners","parents","permissionIds","permissions","shared","sharedWithMeTime","sharingUser","viewedByMe","viewedByMeTime","viewersCanCopyContent","writersCanShare") } Full { @("appProperties","capabilities","contentHints","createdTime","description","explicitlyTrashed","fileExtension","folderColorRgb","fullFileExtension","hasAugmentedPermissions","hasThumbnail","headRevisionId","iconLink","id","imageMediaMetadata","isAppAuthorized","kind","lastModifyingUser","md5Checksum","mimeType","modifiedByMe","modifiedByMeTime","modifiedTime","name","originalFilename","ownedByMe","owners","parents","permissionIds","permissions","properties","quotaBytesUsed","shared","sharedWithMeTime","sharingUser","size","spaces","starred","teamDriveId","thumbnailLink","thumbnailVersion","trashed","trashedTime","trashingUser","version","videoMediaMetadata","viewedByMe","viewedByMeTime","viewersCanCopyContent","webContentLink","webViewLink","writersCanShare") } } } elseif ($PSBoundParameters.ContainsKey('Fields')) { $fs = $Fields } } 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.Drive.v3.DriveService' User = $User } $service = New-GoogleService @serviceParams try { foreach ($file in $FileId) { $backupPath = $null $request = $service.Files.Get($file) $request.SupportsAllDrives = $true if ($fs) { $request.Fields = $($fs -join ",") } $res = $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru if ($OutFilePath) { if (Test-Path $OutFilePath) { $outFilePathItem = Get-Item $OutFilePath -ErrorAction SilentlyContinue if ($outFilePathItem.PSIsContainer) { $resPath = $outFilePathItem.FullName $cleanedName = Get-SafeFileName "$($res.Name).$($res.FileExtension)" $filePath = Join-Path $resPath $cleanedName } elseif ($Force) { $endings = [System.Collections.Generic.List[string]]@('.bak') 1..100 | ForEach-Object {$endings.Add(".bak$_")} foreach ($end in $endings) { $backupPath = "$($outFilePathItem.FullName)$end" if (-not (Test-Path $backupPath)) { break } else { $backupPath = $null } } Write-Warning "Renaming '$($outFilePathItem.Name)' to '$($outFilePathItem.Name).bak' in case replacement download fails." Rename-Item $outFilePathItem.FullName -NewName $backupPath -Force $filePath = $OutFilePath } else { throw "File already exists at path '$($OutFilePath)'. Please specify -Force to overwrite any files with the same name if they exist." } } else { $filePath = $OutFilePath } Write-Verbose "Saving file to path '$filePath'" $stream = [System.IO.File]::Create($filePath) $request.Download($stream) $stream.Close() $res | Add-Member -MemberType NoteProperty -Name OutFilePath -Value $filePath -Force if ($backupPath) { Write-Verbose "File has been downloaded successfully! Removing the backup file at path: $backupPath" Remove-Item $backupPath -Recurse -Force -Confirm:$false } } $res } } catch { $err = $_ if ($backupPath) { if (Test-Path $outFilePathItem.FullName) { Remove-Item $outFilePathItem.FullName -Recurse -Force } Rename-Item $backupPath -NewName $outFilePathItem.FullName -Force } if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($err) } else { Write-Error $err } } } } Export-ModuleMember -Function 'Get-GSDriveFile' function Get-GSDriveFileList { <# .SYNOPSIS Gets the list of Drive files owned by the user .DESCRIPTION Gets the list of Drive files owned by the user .PARAMETER User The email or unique Id of the user whose Drive files you are trying to list Defaults to the AdminEmail user .PARAMETER Filter A query for filtering the file results. See the "Search for Files and Team Drives" guide for the supported syntax: https://developers.google.com/drive/v3/web/search-parameters PowerShell filter syntax here is supported as "best effort". Please use Google's filter operators and syntax to ensure best results .PARAMETER TeamDriveId ID of Team Drive to search .PARAMETER ParentFolderId ID of parent folder to search to add to the filter .PARAMETER Recurse If True, recurses through subfolders found underneath primary search results .PARAMETER IncludeTeamDriveItems Whether Team Drive items should be included in results. (Default: false) .PARAMETER Corpora Comma-separated list of bodies of items (files/documents) to which the query applies. Supported bodies are 'User', 'Domain', 'TeamDrive' and 'AllTeamDrives'. 'AllTeamDrives' must be combined with 'User'; all other values must be used in isolation. Prefer 'User' or 'TeamDrive' to 'AllTeamDrives' for efficiency. .PARAMETER Fields The specific fields to fetch for the listed files. .PARAMETER Spaces A comma-separated list of spaces to query within the corpus. Supported values are 'Drive', 'AppDataFolder' and 'Photos'. .PARAMETER OrderBy A comma-separated list of sort keys. Valid keys are 'createdTime', 'folder', 'modifiedByMeTime', 'modifiedTime', 'name', 'name_natural', 'quotaBytesUsed', 'recency', 'sharedWithMeTime', 'starred', and 'viewedByMeTime'. .PARAMETER PageSize The page size of the result set .PARAMETER Limit The maximum amount of results you want returned. Exclude or set to 0 to return all results .EXAMPLE Get-GSDriveFileList joe Gets Joe's Drive file list #> [OutputType('Google.Apis.Drive.v3.Data.File')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false)] [Alias('Q','Query')] [String[]] $Filter, [parameter(Mandatory = $false)] [String] $TeamDriveId, [parameter(Mandatory = $false)] [String] $ParentFolderId, [parameter(Mandatory = $false)] [Switch] $Recurse, [parameter(Mandatory = $false)] [Switch] $IncludeTeamDriveItems, [parameter(Mandatory = $false)] [String[]] $Fields = @('files','kind','nextPageToken'), [parameter(Mandatory = $false)] [ValidateSet('user','domain','teamDrive')] [String] $Corpora, [parameter(Mandatory = $false)] [ValidateSet('drive','appDataFolder','photos')] [String[]] $Spaces, [parameter(Mandatory = $false)] [ValidateSet('createdTime','folder','modifiedByMeTime','modifiedTime','name','quotaBytesUsed','recency','sharedWithMeTime','starred','viewedByMeTime')] [String[]] $OrderBy, [parameter(Mandatory = $false)] [Alias('MaxResults')] [ValidateRange(1,1000)] [Int] $PageSize = 1000, [parameter(Mandatory = $false)] [Alias('First')] [Int] $Limit = 0 ) Begin { if ($TeamDriveId) { $PSBoundParameters['Corpora'] = 'teamDrive' $PSBoundParameters['IncludeTeamDriveItems'] = $true } if ($ParentFolderId) { if ($Filter) { $Filter += "'$ParentFolderId' in parents" $PSBoundParameters['Filter'] += "'$ParentFolderId' in parents" } else { $Filter = @("'$ParentFolderId' in parents") $PSBoundParameters['Filter'] = @("'$ParentFolderId' in parents") } } if ($Fields -notcontains '*' -and $Fields -notcontains 'nextPageToken') { $Fields += 'nextPageToken' } } Process { Resolve-Email ([ref]$User) $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/drive' ServiceType = 'Google.Apis.Drive.v3.DriveService' User = $User } $service = New-GoogleService @serviceParams try { $request = $service.Files.List() $request.SupportsAllDrives = $true 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) $PageSize = $Limit } $request.PageSize = $PageSize if ($Fields) { $request.Fields = "$($Fields -join ",")" } foreach ($key in $PSBoundParameters.Keys | Where-Object { $_ -notin @('Fields','PageSize') }) { switch ($key) { Filter { $FilterFmt = ($PSBoundParameters[$key] -join " and ") -replace " -eq ","=" -replace " -like ",":" -replace " -match ",":" -replace " -contains ",":" -creplace "'True'","True" -creplace "'False'","False" -replace " -in "," in " -replace " -le ",'<=' -replace " -ge ",">=" -replace " -gt ",'>' -replace " -lt ",'<' -replace " -ne ","!=" -replace " -and "," and " -replace " -or "," or " -replace " -not "," not " $request.Q = $FilterFmt } Spaces { $request.$key = $($PSBoundParameters[$key] -join ",") } Default { if ($request.PSObject.Properties.Name -contains $key) { $request.$key = $PSBoundParameters[$key] } } } } $baseVerbose = "Getting" if ($Fields) { $baseVerbose += " Fields [$($Fields -join ",")] of" } $baseVerbose += " all Drive Files for User '$User'" if ($FilterFmt) { $baseVerbose += " matching Filter: $FilterFmt" } Write-Verbose $baseVerbose [int]$i = 1 $overLimit = $false $originalLimit = $Limit do { $result = $request.Execute() $result.Files | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru [int]$retrieved = ($i + $result.Files.Count) - 1 if ($Recurse -and ($Limit -eq 0 -or $retrieved -lt $Limit)) { Write-Verbose "Starting recursive search..." if ($Limit -gt 0) { $Limit = $Limit - $result.Files.Count Write-Verbose "[Prerecursion] Limit reduced to $Limit to account for $($result.Files.Count) files found in main search" } foreach ($subFolder in ($result.Files | Where-Object { $_.MimeType -eq 'application/vnd.google-apps.folder' } | Sort-Object Name)) { Write-Verbose "Getting recursive file list of files under subfolder $($subFolder.Name) [$($subFolder.Id)]" $params = @{ PageSize = $PageSize User = $User ParentFolderId = $subfolder.Id IncludeTeamDriveItems = $IncludeTeamDriveItems Recurse = $true Fields = $Fields Limit = $Limit Verbose = $false } Get-GSDriveFileList @params -OutVariable sub Write-Verbose "Found $($sub.Count) files in subfolder $($subfolder.Name) [$($subfolder.Id)]" [int]$retrieved += $sub.Count if ($originalLimit -gt 0) { $Limit = $originalLimit - $retrieved Write-Verbose "[Postrecursion] Limit reduced to $Limit" if ($retrieved -ge $originalLimit) { break } } } } if ($result.NextPageToken) { $request.PageToken = $result.NextPageToken } Write-Verbose "Retrieved $retrieved Files..." if ($originalLimit -gt 0 -and $retrieved -ge $originalLimit) { Write-Verbose "Limit reached: $originalLimit" $overLimit = $true } elseif ($originalLimit -gt 0 -and ($retrieved + $PageSize) -gt $originalLimit) { $newPS = $originalLimit - $retrieved Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize,$newPS) $request.PageSize = $newPS } [int]$i = $i + $result.Files.Count } until ($overLimit -or !$result.NextPageToken) } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSDriveFileList' function Get-GSDriveFileUploadStatus { <# .SYNOPSIS Gets the current Drive file upload status .DESCRIPTION Gets the current Drive file upload status .PARAMETER Id The upload Id for the task you'd like to retrieve the status of .PARAMETER InProgress If passed, only returns upload statuses that are not 'Failed' or 'Completed'. If nothing is returned when passing this parameter, all tracked uploads have stopped .EXAMPLE Get-GSDriveFileUploadStatus -InProgress Gets the upload status for all tasks currently in progress #> [CmdletBinding()] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Int[]] $Id, [parameter(Mandatory = $false)] [Switch] $InProgress ) Begin { Write-Verbose "Getting Drive File Upload status" } Process { if ($script:DriveUploadTasks) { foreach ($task in $script:DriveUploadTasks) { $elapsed = ((Get-Date) - $task.StartTime) $progress = {$task.Request.GetProgress()}.InvokeReturnAsIs() if (-not $task.StreamDisposed -and $progress.Status -eq 'Completed') { try { Write-Verbose "[$($progress.Status)] Disposing stream of Task Id [$($task.Id)] | File [$($task.File.FullName)]" $task.Stream.Dispose() $task.StreamDisposed = $true } catch { Write-Error $_ } } $bytesSent = $progress.BytesSent $remaining = try { New-TimeSpan -Seconds $(($elapsed.TotalSeconds / ($bytesSent / ($task.Length))) - $elapsed.TotalSeconds) -ErrorAction Stop } catch { New-TimeSpan } $percentComplete = if ($bytesSent) { [Math]::Round((($bytesSent / $task.Length) * 100),4) } else { 0 } if (-not $InProgress -or $progress.Status -notin @('Failed','Completed')) { if ($Id) { if ($Id -contains $task.Id) { [PSCustomObject]@{ Id = $task.Id Status = $progress.Status PercentComplete = $percentComplete Remaining = $remaining StartTime = $task.StartTime Elapsed = $elapsed File = $task.File.FullName Length = $task.Length Parents = $task.Parents BytesSent = $bytesSent FileLocked = $(Test-FileLock -Path $task.File) User = $task.User Exception = $progress.Exception } } } else { [PSCustomObject]@{ Id = $task.Id Status = $progress.Status PercentComplete = $percentComplete Remaining = $remaining StartTime = $task.StartTime Elapsed = $elapsed File = $task.File.FullName Length = $task.Length Parents = $task.Parents BytesSent = $bytesSent FileLocked = $(Test-FileLock -Path $task.File) User = $task.User Exception = $progress.Exception } } } } } } } Export-ModuleMember -Function 'Get-GSDriveFileUploadStatus' function Get-GSDriveFolderSize { <# .SYNOPSIS Gets the size of the files with the specified ParentFolderId in Drive. .DESCRIPTION Gets the size of the files with the specified ParentFolderId in Drive. .PARAMETER ParentFolderId ID of parent folder to search to add to the filter .PARAMETER Recurse If True, recurses through subfolders found underneath primary search results .PARAMETER Depth Internal use only. Used to track how deep in the subfolder structure the command is currently searching when used with -Verbose .EXAMPLE Get-GSDriveFolderSize -ParentFolderId $id1,$id2 -Recurse #> [CmdletBinding()] Param ( [parameter(Mandatory,Position = 0,ValueFromPipelineByPropertyName)] [Alias('Id')] [String[]] $ParentFolderId, [parameter()] [Switch] $Recurse, [parameter()] [Int] $Depth = 0 ) Begin { $final = @{ TotalSize = 0 } } Process { foreach ($id in $ParentFolderId) { $files = Get-GSDriveFileList -ParentFolderId $id -IncludeTeamDriveItems -Fields "files(name, id, size, mimeType)" -Verbose:$false $folderTotal = ($files.Size | Measure-Object -Sum).Sum if ($folderTotal){ Write-Verbose ("Total file size in bytes in folder ID '$id': {0}" -f $folderTotal) $final.TotalSize += $folderTotal } if ($Recurse -and ($subfolders = $files | Where-Object {$_.MimeType -eq 'application/vnd.google-apps.folder'})) { $newDepth = $Depth + 1 Write-Verbose "[Depth: $Depth > $newDepth] Recursively searching subfolder Ids: [ $($subfolders.Id -join ", ") ]" $subFolderTotal = Get-GSDriveFolderSize -ParentFolderId $subfolders.Id -Recurse -Depth $newDepth if ($subFolderTotal) { $final.TotalSize += $subFolderTotal.TotalSize } } } } End { $final['TotalSizeInMB'] = $final['TotalSize'] / 1MB $final['TotalSizeInGB'] = $final['TotalSize'] / 1GB [PSCustomObject]$final } } Export-ModuleMember -Function 'Get-GSDriveFolderSize' function Get-GSDrivePermission { <# .SYNOPSIS Gets permission information for a Drive file .DESCRIPTION Gets permission information for a Drive file .PARAMETER User The email or unique Id of the user whose Drive file permission you are trying to get Defaults to the AdminEmail user .PARAMETER FileId The unique Id of the Drive file .PARAMETER PermissionId The unique Id of the permission you are trying to get. If excluded, the list of permissions for the Drive file will be returned instead .PARAMETER PageSize The page size of the result set .PARAMETER Limit The maximum amount of results you want returned. Exclude or set to 0 to return all results .EXAMPLE Get-GSDrivePermission -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976' Gets the list of permissions for the file Id #> [OutputType('Google.Apis.Drive.v3.Data.Permission')] [cmdletbinding(DefaultParameterSetName = "List")] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias('Id')] [String] $FileId, [parameter(Mandatory = $false,Position = 1,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false,ParameterSetName = "Get")] [String[]] $PermissionId, [parameter(Mandatory = $false,ParameterSetName = "List")] [Alias('MaxResults')] [ValidateRange(1,100)] [Int] $PageSize = 100, [parameter(Mandatory = $false,ParameterSetName = "List")] [Alias('First')] [Int] $Limit = 0 ) 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.Drive.v3.DriveService' User = $User } $service = New-GoogleService @serviceParams try { if ($PermissionId) { foreach ($per in $PermissionId) { $request = $service.Permissions.Get($FileId,$per) $request.SupportsAllDrives = $true $request.Fields = "*" Write-Verbose "Getting Permission Id '$per' on File '$FileId' for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru | Add-Member -MemberType NoteProperty -Name 'FileId' -Value $FileId -PassThru } } else { $request = $service.Permissions.List($FileId) $request.SupportsAllDrives = $true 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) $PageSize = $Limit } $request.PageSize = $PageSize $request.Fields = "*" Write-Verbose "Getting Permission list on File '$FileId' for user '$User'" [int]$i = 1 $overLimit = $false do { $result = $request.Execute() $result.Permissions | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru | Add-Member -MemberType NoteProperty -Name 'FileId' -Value $FileId -PassThru if ($result.NextPageToken) { $request.PageToken = $result.NextPageToken } [int]$retrieved = ($i + $result.Permissions.Count) - 1 Write-Verbose "Retrieved $retrieved Permissions..." if ($Limit -gt 0 -and $retrieved -eq $Limit) { Write-Verbose "Limit reached: $Limit" $overLimit = $true } elseif ($Limit -gt 0 -and ($retrieved + $PageSize) -gt $Limit) { $newPS = $Limit - $retrieved Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize,$newPS) $request.PageSize = $newPS } [int]$i = $i + $result.Permissions.Count } until (!$result.NextPageToken) } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSDrivePermission' function Get-GSDriveProfile { <# .SYNOPSIS Gets Drive profile for the user .DESCRIPTION Gets Drive profile for the user .PARAMETER User The user to get profile of Defaults to the AdminEmail user .PARAMETER Fields The specific fields to request .EXAMPLE Get-GSDriveProfile Gets the Drive profile of the AdminEmail user #> [OutputType('Google.Apis.Drive.v3.Data.About')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false,Position = 1)] [ValidateSet('AppInstalled','ExportFormats','FolderColorPalette','ImportFormats','Kind','MaxImportSizes','MaxUploadSize','StorageQuota','TeamDriveThemes','User')] [string[]] $Fields = @('AppInstalled','ExportFormats','FolderColorPalette','ImportFormats','Kind','MaxImportSizes','MaxUploadSize','StorageQuota','TeamDriveThemes','User') ) Begin { $fieldDict = @{ AppInstalled = 'appInstalled' ExportFormats = 'exportFormats' FolderColorPalette = 'folderColorPalette' ImportFormats = 'importFormats' Kind = 'kind' MaxImportSizes = 'maxImportSizes' MaxUploadSize = 'maxUploadSize' StorageQuota = 'storageQuota' TeamDriveThemes = 'teamDriveThemes' User = 'user' } } Process { foreach ($U in $User) { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/drive' ServiceType = 'Google.Apis.Drive.v3.DriveService' User = $U } $service = New-GoogleService @serviceParams try { $request = $service.About.Get() $request.Fields = "$(($Fields | ForEach-Object {$fieldDict[$_]}) -join ",")" Write-Verbose "Getting Drive profile for user '$U'" $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Get-GSDriveProfile' function Get-GSDriveRevision { <# .SYNOPSIS Gets information about a Drive file's revisions .DESCRIPTION Gets information about a Drive file's revisions .PARAMETER FileId The unique Id of the file to get revisions of .PARAMETER RevisionId The unique Id of the revision to get. If excluded, gets the list of revisions for the file .PARAMETER OutFilePath The path to save the revision to .PARAMETER User The email or unique Id of the owner of the Drive file Defaults to the AdminEmail user .PARAMETER Fields The specific fields to be returned .PARAMETER Force If $true, overwrites any existing files at the desired path .PARAMETER PageSize The maximum size of each page to return .PARAMETER Limit The maximum amount of objects to return .EXAMPLE Get-GSDriveFile -FileId $fileId | Get-GSDriveRevision Gets the list of revisions for the file .EXAMPLE Get-GSDriveRevision -FileId $fileId -Limit 1 Gets the most recent revision for the file #> [OutputType('Google.Apis.Drive.v3.Data.Revision')] [cmdletbinding(DefaultParameterSetName = "List")] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias('Id')] [String] $FileId, [parameter(Mandatory = $false,Position = 1,ParameterSetName = "Get")] [String[]] $RevisionId, [parameter(Mandatory = $false,ParameterSetName = "Get")] [Alias('SaveFileTo')] [String] $OutFilePath, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false)] [String[]] $Fields = '*', [parameter(Mandatory = $false,ParameterSetName = "Get")] [Switch] $Force, [parameter(Mandatory = $false,ParameterSetName = "List")] [Alias('MaxResults')] [ValidateRange(1,1000)] [Int] $PageSize = 1000, [parameter(Mandatory = $false,ParameterSetName = "List")] [Alias('First')] [Int] $Limit = 0 ) 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.Drive.v3.DriveService' User = $User } $service = New-GoogleService @serviceParams switch ($PSCmdlet.ParameterSetName) { Get { try { foreach ($revision in $RevisionId) { $request = $service.Revisions.Get($FileId,$revision) $baseVerbose = "Getting" if ($Fields) { $baseVerbose += " Fields [$($Fields -join ",")] of" $request.Fields = $($Fields -join ",") } $baseVerbose += " Drive File Revision Id '$revision' of File Id '$FileId' for User '$User'" Write-Verbose $baseVerbose $res = $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru | Add-Member -MemberType NoteProperty -Name 'FileId' -Value $FileId -PassThru if ($OutFilePath) { if (Test-Path $OutFilePath) { $outFilePathItem = Get-Item $OutFilePath -ErrorAction SilentlyContinue if ($outFilePathItem.PSIsContainer) { $resPath = $outFilePathItem.FullName $cleanedName = Get-SafeFileName "$($res.Name).$($res.FileExtension)" $filePath = Join-Path $resPath $cleanedName } elseif ($Force) { $endings = [System.Collections.Generic.List[string]]@('.bak') 1..100 | ForEach-Object {$endings.Add(".bak$_")} foreach ($end in $endings) { $backupPath = "$($outFilePathItem.FullName)$end" if (-not (Test-Path $backupPath)) { break } else { $backupPath = $null } } Write-Warning "Renaming '$($outFilePathItem.Name)' to '$($outFilePathItem.Name).bak' in case replacement download fails." Rename-Item $outFilePathItem.FullName -NewName $backupPath -Force $filePath = $OutFilePath } else { throw "File already exists at path '$($OutFilePath)'. Please specify -Force to overwrite any files with the same name if they exist." } } else { $filePath = $OutFilePath } Write-Verbose "Saving file to path '$filePath'" $stream = [System.IO.File]::Create($filePath) $request.Download($stream) $stream.Close() $res | Add-Member -MemberType NoteProperty -Name OutFilePath -Value $filePath -Force if ($backupPath) { Write-Verbose "File has been downloaded successfully! Removing the backup file at path: $backupPath" Remove-Item $backupPath -Recurse -Force -Confirm:$false } } $res } } catch { $err = $_ if ($backupPath) { if (Test-Path $outFilePathItem.FullName) { Remove-Item $outFilePathItem.FullName -Recurse -Force } Rename-Item $backupPath -NewName $outFilePathItem.FullName -Force } if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($err) } else { Write-Error $err } } } List { try { $request = $service.Revisions.List($FileId) 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) $PageSize = $Limit } $request.PageSize = $PageSize if ($Fields) { $request.Fields = "$($Fields -join ",")" } $baseVerbose = "Getting" if ($Fields) { $baseVerbose += " Fields [$($Fields -join ",")] of" } $baseVerbose += " all Drive File Revisions of File Id '$FileId' for User '$User'" Write-Verbose $baseVerbose [int]$i = 1 $overLimit = $false do { $result = $request.Execute() $result.Revisions | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru | Add-Member -MemberType NoteProperty -Name 'FileId' -Value $FileId -PassThru if ($result.NextPageToken) { $request.PageToken = $result.NextPageToken } [int]$retrieved = ($i + $result.Revisions.Count) - 1 Write-Verbose "Retrieved $retrieved Revisions..." if ($Limit -gt 0 -and $retrieved -eq $Limit) { Write-Verbose "Limit reached: $Limit" $overLimit = $true } elseif ($Limit -gt 0 -and ($retrieved + $PageSize) -gt $Limit) { $newPS = $Limit - $retrieved Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize,$newPS) $request.PageSize = $newPS } [int]$i = $i + $result.Revisions.Count } until ($overLimit -or !$result.NextPageToken) } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } } Export-ModuleMember -Function 'Get-GSDriveRevision' function Hide-GSDrive { <# .SYNOPSIS Hides a Shared Drive from the default view .DESCRIPTION Hides a Shared Drive from the default view .PARAMETER DriveId The unique Id of the Shared Drive to hide .PARAMETER User The email or unique Id of the user who you'd like to hide the Shared Drive for. Defaults to the AdminEmail user. .EXAMPLE Hide-GSDrive -DriveId $driveIds Hides the specified DriveIds for the AdminEmail user #> [OutputType('Google.Apis.Drive.v3.Data.Drive')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = "Get")] [Alias('Id','TeamDriveId')] [String[]] $DriveId, [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail ) 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.Drive.v3.DriveService' User = $User } $service = New-GoogleService @serviceParams foreach ($id in $DriveId) { try { $request = $service.Drives.Hide($id) Write-Verbose "Hiding Shared Drive '$id' for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Hide-GSDrive' function New-GSDrive { <# .SYNOPSIS Creates a new Shared Drive .DESCRIPTION Creates a new Shared Drive .PARAMETER Name The name of the Shared Drive .PARAMETER User The user to create the Shared Drive for (must have permissions to create Shared Drives) .PARAMETER RequestId An ID, such as a random UUID, which uniquely identifies this user's request for idempotent creation of a Shared Drive. A repeated request by the same user and with the same request ID will avoid creating duplicates by attempting to create the same Shared Drive. If the Shared Drive already exists a 409 error will be returned. .PARAMETER CanAddChildren Whether the current user can add children to folders in this Shared Drive .PARAMETER CanChangeDriveBackground Whether the current user can change the background of this Shared Drive .PARAMETER CanComment Whether the current user can comment on files in this Shared Drive .PARAMETER CanCopy Whether the current user can copy files in this Shared Drive .PARAMETER CanDeleteDrive Whether the current user can delete this Shared Drive. Attempting to delete the Shared Drive may still fail if there are untrashed items inside the Shared Drive .PARAMETER CanDownload Whether the current user can download files in this Shared Drive .PARAMETER CanEdit Whether the current user can edit files in this Shared Drive .PARAMETER CanListChildren Whether the current user can list the children of folders in this Shared Drive .PARAMETER CanManageMembers Whether the current user can add members to this Shared Drive or remove them or change their role .PARAMETER CanReadRevisions Whether the current user can read the revisions resource of files in this Shared Drive .PARAMETER CanRemoveChildren Whether the current user can remove children from folders in this Shared Drive .PARAMETER CanRename Whether the current user can rename files or folders in this Shared Drive .PARAMETER CanRenameDrive Whether the current user can rename this Shared Drive .PARAMETER CanShare Whether the current user can share files or folders in this Shared Drive .EXAMPLE New-GSDrive -Name "Training Docs" Creates a new Shared Drive named "Training Docs" #> [OutputType('Google.Apis.Drive.v3.Data.Drive')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0)] [String] $Name, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String] $RequestId = (New-Guid).ToString('N'), [parameter(Mandatory = $false)] [Switch] $CanAddChildren, [parameter(Mandatory = $false)] [Alias('CanChangeTeamDriveBackground')] [Switch] $CanChangeDriveBackground, [parameter(Mandatory = $false)] [Switch] $CanComment, [parameter(Mandatory = $false)] [Switch] $CanCopy, [parameter(Mandatory = $false)] [Alias('CanDeleteTeamDrive')] [Switch] $CanDeleteDrive, [parameter(Mandatory = $false)] [Switch] $CanDownload, [parameter(Mandatory = $false)] [Switch] $CanEdit, [parameter(Mandatory = $false)] [Switch] $CanListChildren, [parameter(Mandatory = $false)] [Switch] $CanManageMembers, [parameter(Mandatory = $false)] [Switch] $CanReadRevisions, [parameter(Mandatory = $false)] [Switch] $CanRemoveChildren, [parameter(Mandatory = $false)] [Switch] $CanRename, [parameter(Mandatory = $false)] [Alias('CanRenameTeamDrive')] [Switch] $CanRenameDrive, [parameter(Mandatory = $false)] [Switch] $CanShare ) 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.Drive.v3.DriveService' User = $User } $service = New-GoogleService @serviceParams try { $body = New-Object 'Google.Apis.Drive.v3.Data.Drive' $capabilities = New-Object 'Google.Apis.Drive.v3.Data.Drive+CapabilitiesData' foreach ($key in $PSBoundParameters.Keys) { switch ($key) { Default { if ($capabilities.PSObject.Properties.Name -contains $key) { $capabilities.$key = $PSBoundParameters[$key] } elseif ($body.PSObject.Properties.Name -contains $key) { $body.$key = $PSBoundParameters[$key] } } } } $body.Capabilities = $capabilities $request = $service.Drives.Create($body,$RequestId) Write-Verbose "Creating Shared Drive '$Name' for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru | Add-Member -MemberType NoteProperty -Name 'RequestId' -Value $RequestId -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'New-GSDrive' function New-GSDriveFile { <# .SYNOPSIS Creates a blank Drive file .DESCRIPTION Creates a blank Drive file .PARAMETER User The email or unique Id of the user who you are creating the Drive file for Defaults to the AdminEmail user .PARAMETER Name The name of the new Drive file .PARAMETER Description The description of the Drive file .PARAMETER FolderColorRgb The color for a folder as an RGB hex string. Available values are: * "ChocolateIceCream" * "OldBrickRed" * "Cardinal" * "WildStrawberries" * "MarsOrange" * "YellowCab" * "Spearmint" * "VernFern" * "Asparagus" * "SlimeGreen" * "DesertSand" * "Macaroni" * "SeaFoam" * "Pool" * "Denim" * "RainySky" * "BlueVelvet" * "PurpleDino" * "Mouse" * "MountainGrey" * "Earthworm" * "BubbleGum" * "PurpleRain" * "ToyEggplant" .PARAMETER Parents The parent folder Id of the new Drive file .PARAMETER MimeType The Google Mime Type of the new Drive file Available values are: * "Audio" * "Docs" * "Drawing" * "DriveFile" * "DriveFolder" * "Form" * "FusionTables" * "Map" * "Photo" * "Slides" * "AppsScript" * "Sites" * "Sheets" * "Unknown" * "Video" .PARAMETER CustomMimeType The custom Mime Type of the new Drive file .PARAMETER Projection The defined subset of fields to be returned Available values are: * "Minimal" * "Standard" * "Full" * "Access" .PARAMETER Fields The specific fields to returned .EXAMPLE New-GSDriveFile -Name "Training Docs" -MimeType DriveFolder Creates a new folder in Drive named "Training Docs" in the root OrgUnit for the AdminEmail user #> [OutputType('Google.Apis.Drive.v3.Data.File')] [cmdletbinding(DefaultParameterSetName = "BuiltIn")] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $true)] [String] $Name, [parameter(Mandatory = $false)] [String] $Description, [parameter(Mandatory = $false)] [ValidateSet('ChocolateIceCream','OldBrickRed','Cardinal','WildStrawberries','MarsOrange','YellowCab','Spearmint','VernFern','Asparagus','SlimeGreen','DesertSand','Macaroni','SeaFoam','Pool','Denim','RainySky','BlueVelvet','PurpleDino','Mouse','MountainGrey','Earthworm','BubbleGum','PurpleRain','ToyEggplant')] [string] $FolderColorRgb, [parameter(Mandatory = $false)] [Alias('ParentId')] [String[]] $Parents, [parameter(Mandatory = $true,ParameterSetName = "BuiltIn")] [Alias('Type')] [ValidateSet("Audio","Docs","Drawing","DriveFile","DriveFolder","Form","FusionTables","Map","Photo","Slides","AppsScript","Sites","Sheets","Unknown","Video")] [String] $MimeType, [parameter(Mandatory = $true,ParameterSetName = "Custom")] [String] $CustomMimeType, [parameter(Mandatory = $false)] [Alias('Depth')] [ValidateSet("Minimal","Standard","Full","Access")] [String] $Projection = "Full", [parameter(Mandatory = $false)] [ValidateSet("appProperties","capabilities","contentHints","createdTime","description","explicitlyTrashed","fileExtension","folderColorRgb","fullFileExtension","hasThumbnail","headRevisionId","iconLink","id","imageMediaMetadata","isAppAuthorized","kind","lastModifyingUser","md5Checksum","mimeType","modifiedByMe","modifiedByMeTime","modifiedTime","name","originalFilename","ownedByMe","owners","parents","permissions","properties","quotaBytesUsed","shared","sharedWithMeTime","sharingUser","size","spaces","starred","thumbnailLink","thumbnailVersion","trashed","version","videoMediaMetadata","viewedByMe","viewedByMeTime","viewersCanCopyContent","webContentLink","webViewLink","writersCanShare")] [String[]] $Fields ) Begin { $colorDictionary = @{ ChocolateIceCream = '#ac725e' OldBrickRed = '#d06b64' Cardinal = '#f83a22' WildStrawberries = '#fa573c' MarsOrange = '#ff7537' YellowCab = '#ffad46' Spearmint = '#42d692' VernFern = '#16a765' Asparagus = '#7bd148' SlimeGreen = '#b3dc6c' DesertSand = '#fbe983' Macaroni = '#fad165' SeaFoam = '#92e1c0' Pool = '#9fe1e7' Denim = '#9fc6e7' RainySky = '#4986e7' BlueVelvet = '#9a9cff' PurpleDino = '#b99aff' Mouse = '#8f8f8f' MountainGrey = '#cabdbf' Earthworm = '#cca6ac' BubbleGum = '#f691b2' PurpleRain = '#cd74e6' ToyEggplant = '#a47ae2' } $mimeHash = @{ Audio = "application/vnd.google-apps.audio" Docs = "application/vnd.google-apps.document" Drawing = "application/vnd.google-apps.drawing" DriveFile = "application/vnd.google-apps.file" DriveFolder = "application/vnd.google-apps.folder" Form = "application/vnd.google-apps.form" FusionTables = "application/vnd.google-apps.fusiontable" Map = "application/vnd.google-apps.map" Photo = "application/vnd.google-apps.photo" Slides = "application/vnd.google-apps.presentation" AppsScript = "application/vnd.google-apps.script" Sites = "application/vnd.google-apps.sites" Sheets = "application/vnd.google-apps.spreadsheet" Unknown = "application/vnd.google-apps.unknown" Video = "application/vnd.google-apps.video" } if ($Projection) { $fs = switch ($Projection) { Standard { @("createdTime","description","fileExtension","id","lastModifyingUser","modifiedTime","name","owners","parents","properties","version","webContentLink","webViewLink") } Access { @("createdTime","description","fileExtension","id","lastModifyingUser","modifiedTime","name","ownedByMe","owners","parents","permissionIds","permissions","shared","sharedWithMeTime","sharingUser","viewedByMe","viewedByMeTime","viewersCanCopyContent","writersCanShare") } Full { @("appProperties","capabilities","contentHints","createdTime","description","explicitlyTrashed","fileExtension","folderColorRgb","fullFileExtension","hasAugmentedPermissions","hasThumbnail","headRevisionId","iconLink","id","imageMediaMetadata","isAppAuthorized","kind","lastModifyingUser","md5Checksum","mimeType","modifiedByMe","modifiedByMeTime","modifiedTime","name","originalFilename","ownedByMe","owners","parents","permissionIds","permissions","properties","quotaBytesUsed","shared","sharedWithMeTime","sharingUser","size","spaces","starred","teamDriveId","thumbnailLink","thumbnailVersion","trashed","trashedTime","trashingUser","version","videoMediaMetadata","viewedByMe","viewedByMeTime","viewersCanCopyContent","webContentLink","webViewLink","writersCanShare") } } } elseif ($PSBoundParameters.ContainsKey('Fields')) { $fs = $Fields } } 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.Drive.v3.DriveService' User = $User } $service = New-GoogleService @serviceParams try { $body = New-Object 'Google.Apis.Drive.v3.Data.File' if ($MimeType) { $body.MimeType = $mimeHash[$MimeType] } elseif ($CustomMimeType) { $body.MimeType = $CustomMimeType } if ($Description) { $body.Description = $Description } if ($FolderColorRgb) { $body.FolderColorRgb = $ColorDictionary[$FolderColorRgb] } if ($Name) { $body.Name = [String]$Name } if ($Parents) { $body.Parents = [String[]]$Parents } $request = $service.Files.Create($body) $request.SupportsAllDrives = $true if ($fs) { $request.Fields = $($fs -join ",") } Write-Verbose "Creating file '$Name' for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'New-GSDriveFile' function Remove-GSDrive { <# .SYNOPSIS Removes a Shared Drive .DESCRIPTION Removes a Shared Drive .PARAMETER DriveId The Id of the Shared Drive to remove .PARAMETER User The email or unique Id of the user with permission to delete the Shared Drive Defaults to the AdminEmail user .EXAMPLE Remove-Drive -DriveId "0AJ8Xjq3FcdCKUk9PVA" -Confirm:$false Removes the Shared Drive '0AJ8Xjq3FcdCKUk9PVA', skipping confirmation #> [cmdletbinding(SupportsShouldProcess=$true,ConfirmImpact="High")] Param ( [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [Alias('Id','TeamDriveId')] [String[]] $DriveId, [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail ) 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.Drive.v3.DriveService' User = $User } $service = New-GoogleService @serviceParams try { foreach ($id in $DriveId) { if ($PSCmdlet.ShouldProcess("Deleting Shared Drive '$id' from user '$User'")) { Write-Verbose "Deleting Shared Drive '$id' from user '$User'" $request = $service.Drives.Delete($id) $request.Execute() Write-Verbose "Shared Drive '$id' successfully deleted from user '$User'" } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Remove-GSDrive' function Remove-GSDriveFile { <# .SYNOPSIS Deletes a Drive file .DESCRIPTION Deletes a Drive file .PARAMETER FileId The unique Id(s) of the file(s) to delete .PARAMETER User The email or unique Id of the owner of the Drive file Defaults to the AdminEmail user .EXAMPLE Remove-GSDriveFile -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976' -User user@domain.com Deletes the file with ID 1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976 from the user user@domain.com's Drive .EXAMPLE Get-GSDriveFileList -ParentFolderId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976' -User user@domain.com | Remove-GSDriveFile Get the file with ID 1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976 from the user user@domain.com's Drive and pipeline #> [cmdletbinding(SupportsShouldProcess = $true, ConfirmImpact = "High")] Param( [parameter(Mandatory,ValueFromPipelineByPropertyName,Position = 0)] [Alias('Id')] [String[]] $FileId, [parameter(ValueFromPipelineByPropertyName,Position = 1)] [Alias('Owner', 'PrimaryEmail', 'UserKey', 'Mail')] [string] $User = $Script:PSGSuite.AdminEmail ) Process { Resolve-Email ([ref]$User) $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/drive' ServiceType = 'Google.Apis.Drive.v3.DriveService' User = $User } $service = New-GoogleService @serviceParams foreach ($file in $FileId) { try { if ($PSCmdlet.ShouldProcess("Deleting File Id '$FileId' from user '$User'")) { Write-Verbose "Deleting File Id '$FileId' from user '$User'" $request = $service.Files.Delete($FileId) $request.SupportsAllDrives = $true $request.Execute() | Out-Null Write-Verbose "File Id '$FileId' successfully deleted from user '$User'" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Remove-GSDriveFile' function Remove-GSDrivePermission { <# .SYNOPSIS Removes a permission from a Drive file .DESCRIPTION Removes a permission from a Drive file .PARAMETER User The email or unique Id of the user whose Drive file permission you are trying to get Defaults to the AdminEmail user .PARAMETER FileId The unique Id of the Drive file .PARAMETER PermissionId The unique Id of the permission you are trying to remove. .EXAMPLE Remove-GSDrivePermission -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976' -PermissionID 'sdfadsfsdafasd' Removes the permission from the drive. .EXAMPLE Get-GSDrivePermission -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976' | ? {$_.Type -eq 'group'} | Remove-GSDrivePermission Gets the permissions assigned to groups and removes them. #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] [String] $FileId, [parameter(Mandatory = $false,Position = 1,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [Alias('Id')] [String] $PermissionId ) 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.Drive.v3.DriveService' User = $User } $service = New-GoogleService @serviceParams try { if ($PSCmdlet.ShouldProcess("Removing Drive Permission Id '$PermissionId' from FileId '$FileID'")) { $request = $service.Permissions.Delete($FileId,$PermissionId) $request.SupportsAllDrives = $true $request.Execute() Write-Verbose "Successfully removed Drive Permission Id '$PermissionId' from FileId '$FileID'" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Remove-GSDrivePermission' function Remove-GSDriveRevision { <# .SYNOPSIS Permanently deletes a file version. .DESCRIPTION Permanently deletes a file version. You can only delete revisions for files with binary content in Google Drive, like images or videos. Revisions for other files, like Google Docs or Sheets, and the last remaining file version can't be deleted. .PARAMETER FileId The unique Id of the file to remove revisions from .PARAMETER RevisionId The unique Id of the revision to remove .PARAMETER User The email or unique Id of the owner of the Drive file Defaults to the AdminEmail user .EXAMPLE Get-GSDriveRevision -FileId $fileId -Limit 1 | Remove-GSDriveRevision Removes the oldest revision for the file .EXAMPLE Get-GSDriveRevision -FileId $fileId | Select-Object -Last 1 | Remove-GSDriveRevision Removes the newest revision for the file #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] [String] $FileId, [parameter(Mandatory = $true,Position = 1,ValueFromPipelineByPropertyName = $true)] [Alias('Id')] [String[]] $RevisionId, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail ) 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.Drive.v3.DriveService' User = $User } $service = New-GoogleService @serviceParams foreach ($id in $RevisionId) { try { if ($PSCmdlet.ShouldProcess("Removing Drive Revision Id '$id' from FileId '$FileID' for user '$User'")) { $request = $service.Revisions.Delete($FileId,$id) $request.Execute() Write-Verbose "Successfully removed Drive Revision Id '$id' from FileId '$FileID' for user '$User'" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Remove-GSDriveRevision' function Set-GSDocContent { <# .SYNOPSIS Sets the content of a Google Doc. This overwrites any existing content on the Doc .DESCRIPTION Sets the content of a Google Doc. This overwrites any existing content on the Doc .PARAMETER FileID The unique Id of the file to set content on .PARAMETER Value The content to set .PARAMETER User The email or unique Id of the owner of the Drive file Defaults to the AdminEmail user .EXAMPLE $logStrings | Set-GSDocContent -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976' Sets the content of the specified Google Doc to the strings in the $logStrings variable. Any existing content on the doc will be overwritten. #> [CmdLetBinding()] Param ( [parameter(Mandatory = $true,Position = 0)] [String] $FileID, [parameter(Mandatory = $true,ValueFromPipeline = $true)] [String[]] $Value, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail ) Begin { $service = New-GoogleService @serviceParams $stream = New-Object 'System.IO.MemoryStream' $writer = New-Object 'System.IO.StreamWriter' $stream $concatStrings = @() } 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.Drive.v3.DriveService' User = $User } foreach ($string in $Value) { $concatStrings += $string } } End { try { $concatStrings = $concatStrings -join "`n" $writer.Write($concatStrings) $writer.Flush() $contentType = 'text/plain' $body = New-Object 'Google.Apis.Drive.v3.Data.File' $request = $service.Files.Update($body,$FileId,$stream,$contentType) $request.QuotaUser = $User $request.ChunkSize = 512KB $request.SupportsAllDrives = $true Write-Verbose "Setting content for File '$FileID'" $request.Upload() | Out-Null $stream.Close() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Set-GSDocContent' function Show-GSDrive { <# .SYNOPSIS Shows (unhides) a Shared Drive in the default view .DESCRIPTION Shows (unhides) a Shared Drive in the default view .PARAMETER DriveId The unique Id of the Shared Drive to unhide .PARAMETER User The email or unique Id of the user who you'd like to unhide the Shared Drive for. Defaults to the AdminEmail user. .EXAMPLE Show-GSDrive -DriveId $driveIds Unhides the specified DriveIds for the AdminEmail user #> [OutputType('Google.Apis.Drive.v3.Data.Drive')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = "Get")] [Alias('Id','TeamDriveId')] [String[]] $DriveId, [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail ) 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.Drive.v3.DriveService' User = $User } $service = New-GoogleService @serviceParams foreach ($id in $DriveId) { try { $request = $service.Drives.Unhide($id) Write-Verbose "Unhiding Shared Drive '$id' for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Show-GSDrive' function Start-GSDriveFileUpload { <# .SYNOPSIS Starts uploading a file or list of files to Drive asynchronously .DESCRIPTION Starts uploading a file or list of files to Drive asynchronously. Allows full folder structure uploads by passing a folder as -Path and including the -Recurse parameter .PARAMETER Path The path of the file or folder to upload .PARAMETER Description The description of the file or folder in Drive .PARAMETER Parents The unique Id of the parent folder in Drive to upload the file to Defaults to the root folder in My Drive .PARAMETER Recurse If $true and there is a Directory passed to -Path, this will rebuild the folder structure in Drive under the Parent Id and upload the files within accordingly .PARAMETER Wait If $true, waits for all uploads to complete and shows progress around the total upload .PARAMETER RetryCount How many times uploads should be retried when using the -Wait parameter Defaults to 10 .PARAMETER ThrottleLimit The limit of files to upload per batch while waiting .PARAMETER User The email or unique Id of the user to upload the files for .EXAMPLE Start-GSDriveFileUpload -Path "C:\Scripts","C:\Modules" -Recurse -Wait Starts uploading the Scripts and Modules folders and the files within them and waits for the uploads to complete, showing progress as files are uploaded #> [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias('FullName')] [ValidateScript( {Test-Path $_})] [String[]] $Path, [parameter(Mandatory = $false)] [String] $Description, [parameter(Mandatory = $false)] [String[]] $Parents, [parameter(Mandatory = $false)] [Switch] $Recurse, [parameter(Mandatory = $false)] [Switch] $Wait, [parameter(Mandatory = $false)] [Int] $RetryCount = 10, [parameter(Mandatory = $false)] [ValidateRange(1,1000)] [Int] $ThrottleLimit = 20, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail ) Begin { $taskList = [System.Collections.ArrayList]@() $fullTaskList = [System.Collections.ArrayList]@() $start = Get-Date $folIdHash = @{} $throttleCount = 0 $totalThrottleCount = 0 $totalFiles = 0 $successParam = @{ Successful = $false } } 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.Drive.v3.DriveService' User = $User } $service = New-GoogleService @serviceParams try { foreach ($file in $Path) { $details = Get-Item $file if ($details.PSIsContainer) { $newFolPerms = @{ Name = $details.Name User = $User Type = 'DriveFolder' Verbose = $false } $parentVerbose = $null if ($PSBoundParameters.Keys -contains 'Parents') { $newFolPerms['Parents'] = $PSBoundParameters['Parents'] if ($par = Get-GSDriveFileInfo -FileId $PSBoundParameters['Parents'] -User $User -Verbose:$false -ErrorAction Stop) { $parentVerbose = " under parent folder '$($par.Name -join ',')'" } } Write-Verbose "Creating new Drive folder '$($details.Name)'$parentVerbose" $id = New-GSDriveFile @newFolPerms | Select-Object -ExpandProperty Id $folIdHash[$details.FullName.TrimEnd('\').TrimEnd('/')] = $id if ($Recurse) { $recurseList = Get-ChildItem $details.FullName -Recurse $recDirs = $recurseList | Where-Object {$_.PSIsContainer} | Sort-Object FullName if ($recDirs) { Write-Verbose "Creating recursive folder structure under '$($details.Name)'" $recDirs | ForEach-Object { $parPath = "$(Split-Path $_.FullName -Parent)" $newFolPerms = @{ Name = $_.Name User = $User Type = 'DriveFolder' Parents = [String[]]$folIdHash[$parPath] Verbose = $false } $id = New-GSDriveFile @newFolPerms | Select-Object -ExpandProperty Id $folIdHash[$_.FullName] = $id Write-Verbose " + Created: [ID $id] $($_.FullName -replace "^$([RegEx]::Escape($details.FullName))",$details.Name)" } } $details = $recurseList | Where-Object {!$_.PSIsContainer} | Sort-Object FullName $checkFolIdHash = $true $totalFiles = [int]$totalFiles + $details.Count } } else { $totalFiles++ $checkFolIdHash = $false } foreach ($detPart in $details) { $throttleCount++ $contentType = Get-MimeType $detPart $body = New-Object 'Google.Apis.Drive.v3.Data.File' -Property @{ Name = [String]$detPart.Name } if (!$checkFolIdHash -and ($PSBoundParameters.Keys -contains 'Parents')) { if ($Parents) { $body.Parents = [String[]]$Parents } } elseif ($checkFolIdHash) { $parPath = "$(Split-Path $detPart.FullName -Parent)" $body.Parents = [String[]]$folIdHash[$parPath] } if ($Description) { $body.Description = $Description } $stream = New-Object 'System.IO.FileStream' $detPart.FullName,([System.IO.FileMode]::Open),([System.IO.FileAccess]::Read),([System.IO.FileShare]"Delete, ReadWrite") $request = $service.Files.Create($body,$stream,$contentType) $request.QuotaUser = $User $request.SupportsAllDrives = $true $request.ChunkSize = 512KB $upload = $request.UploadAsync() $task = $upload.ContinueWith([System.Action[System.Threading.Tasks.Task]] {if ($stream) { $stream.Dispose() }}) Write-Verbose "[$($detPart.Name)] Upload Id $($upload.Id) has started" if (!$Script:DriveUploadTasks) { $Script:DriveUploadTasks = [System.Collections.ArrayList]@() } $script:DriveUploadTasks += [PSCustomObject]@{ Id = $upload.Id File = $detPart Length = $detPart.Length SizeInMB = [Math]::Round(($detPart.Length / 1MB),2,[MidPointRounding]::AwayFromZero) StartTime = $(Get-Date) Parents = $body.Parents User = $User Upload = $upload Request = $request Stream = $stream StreamDisposed = $false } $taskList += [PSCustomObject]@{ Id = $upload.Id File = $detPart SizeInMB = [Math]::Round(($detPart.Length / 1MB),2,[MidPointRounding]::AwayFromZero) User = $User } $fullTaskList += [PSCustomObject]@{ Id = $upload.Id File = $detPart SizeInMB = [Math]::Round(($detPart.Length / 1MB),2,[MidPointRounding]::AwayFromZero) User = $User } if ($throttleCount -ge $ThrottleLimit) { $totalThrottleCount += $throttleCount if ($Wait) { Watch-GSDriveUpload -Id $taskList.Id -CountUploaded $totalThrottleCount -TotalUploading $totalFiles $throttleCount = 0 $taskList = [System.Collections.ArrayList]@() } } } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } finally { try { [System.Console]::CursorVisible = $true } catch { if ($Error[0].Exception.Message -eq 'Exception setting "CursorVisible": "The handle is invalid."') { $Global:Error.Remove($Global:Error[0]) } } } } End { if (-not $Wait) { $fullTaskList } else { try { Watch-GSDriveUpload -Id $fullTaskList.Id -CountUploaded $totalFiles -TotalUploading $totalFiles $fullStatusList = Get-GSDriveFileUploadStatus -Id $fullTaskList.Id $failedFiles = $fullStatusList | Where-Object {$_.Status -eq "Failed"} if (!$failedFiles) { Write-Verbose "All files uploaded to Google Drive successfully! Total time: $("{0:c}" -f ((Get-Date) - $start) -replace "\..*")" $successParam['Successful'] = $true } elseif ($RetryCount) { $totalRetries = 0 do { $throttleCount = 0 $totalThrottleCount = 0 $taskList = [System.Collections.ArrayList]@() $fullTaskList = [System.Collections.ArrayList]@() $details = Get-Item $failedFiles.File $totalFiles = [int]$totalFiles + $details.Count $totalRetries++ Write-Verbose "~ ~ ~ RETRYING [$totalFiles] FAILED FILES [Retry # $totalRetries / $RetryCount] ~ ~ ~" $details = Get-Item $failedFiles.File foreach ($detPart in $details) { $throttleCount++ $contentType = Get-MimeType $detPart $body = New-Object 'Google.Apis.Drive.v3.Data.File' -Property @{ Name = [String]$detPart.Name } $parPath = "$(Split-Path $detPart.FullName -Parent)" $body.Parents = [String[]]$folIdHash[$parPath] if ($Description) { $body.Description = $Description } $stream = New-Object 'System.IO.FileStream' $detPart.FullName,([System.IO.FileMode]::Open),([System.IO.FileAccess]::Read),([System.IO.FileShare]::Delete + [System.IO.FileShare]::ReadWrite) $request = $service.Files.Create($body,$stream,$contentType) $request.QuotaUser = $User $request.SupportsAllDrives = $true $request.ChunkSize = 512KB $upload = $request.UploadAsync() $task = $upload.ContinueWith([System.Action[System.Threading.Tasks.Task]] {$stream.Dispose()}) Write-Verbose "[$($detPart.Name)] Upload Id $($upload.Id) has started" if (!$Script:DriveUploadTasks) { $Script:DriveUploadTasks = [System.Collections.ArrayList]@() } $script:DriveUploadTasks += [PSCustomObject]@{ Id = $upload.Id File = $detPart Length = $detPart.Length SizeInMB = [Math]::Round(($detPart.Length / 1MB),2,[MidPointRounding]::AwayFromZero) StartTime = $(Get-Date) Parents = $body.Parents User = $User Upload = $upload Request = $request Stream = $stream StreamDisposed = $false } $taskList += [PSCustomObject]@{ Id = $upload.Id File = $detPart SizeInMB = [Math]::Round(($detPart.Length / 1MB),2,[MidPointRounding]::AwayFromZero) User = $User } $fullTaskList += [PSCustomObject]@{ Id = $upload.Id File = $detPart SizeInMB = [Math]::Round(($detPart.Length / 1MB),2,[MidPointRounding]::AwayFromZero) User = $User } if ($throttleCount -ge $ThrottleLimit) { $totalThrottleCount += $throttleCount if ($Wait) { Watch-GSDriveUpload -Id $taskList.Id -CountUploaded $totalThrottleCount -TotalUploading $totalFiles -Action Retrying $throttleCount = 0 $taskList = [System.Collections.ArrayList]@() } } } Watch-GSDriveUpload -Id $fullTaskList.Id -Action Retrying -CountUploaded $totalFiles -TotalUploading $totalFiles $fullStatusList = Get-GSDriveFileUploadStatus -Id $fullTaskList.Id $failedFiles = $fullStatusList | Where-Object {$_.Status -eq "Failed"} } until (!$failedFiles -or ($totalRetries -ge $RetryCount)) if ($failedFiles) { Write-Warning "The following files failed to upload:`n`n$($failedFiles | Select-Object Id,Status,Exception,File | Format-List | Out-String)" } elseif (!$failedFiles) { Write-Verbose "All files uploaded to Google Drive successfully! Total time: $("{0:c}" -f ((Get-Date) - $start) -replace "\..*")" $successParam['Successful'] = $true } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } finally { if ($Host.Name -and $Host.Name -notlike "Windows*PowerShell*ISE*") { try { [System.Console]::CursorVisible = $true } catch { if ($Error[0].Exception.Message -eq 'Exception setting "CursorVisible": "The handle is invalid."') { $Global:Error.Remove($Global:Error[0]) } } } Stop-GSDriveFileUpload @successParam } } } } Export-ModuleMember -Function 'Start-GSDriveFileUpload' function Stop-GSDriveFileUpload { <# .SYNOPSIS Stops all Drive file uploads in progress and disposes of all streams. .DESCRIPTION Stops all Drive file uploads in progress and disposes of all streams. .PARAMETER Successful If $true, hides any failed task verbose output .EXAMPLE Stop-GSDriveFileUpload Stops all Drive file uploads in progress and disposes of all streams. #> [cmdletbinding()] Param ( [Parameter(DontShow)] [Switch] $Successful ) Begin { Write-Verbose "Stopping all remaining Drive file uploads!" } Process { foreach ($task in $script:DriveUploadTasks | Where-Object {$_.Stream -and -not $_.StreamDisposed}) { try { if (-not $Successful) { $progress = {$task.Request.GetProgress()}.InvokeReturnAsIs() Write-Verbose "[$($progress.Status)] Stopping stream of Task Id [$($task.Id)] | File [$($task.File.FullName)]" } $task.Stream.Dispose() $task.StreamDisposed = $true } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Stop-GSDriveFileUpload' function Update-GSDrive { <# .SYNOPSIS Update metatdata for a Shared Drive .DESCRIPTION Update metatdata for a Shared Drive .PARAMETER DriveId The unique Id of the Shared Drive to update .PARAMETER User The user to create the Shared Drive for (must have permissions to create Shared Drives) .PARAMETER Name The name of the Shared Drive .PARAMETER CanAddChildren Whether the current user can add children to folders in this Shared Drive .PARAMETER CanChangeDriveBackground Whether the current user can change the background of this Shared Drive .PARAMETER CanComment Whether the current user can comment on files in this Shared Drive .PARAMETER CanCopy Whether the current user can copy files in this Shared Drive .PARAMETER CanDeleteDrive Whether the current user can delete this Shared Drive. Attempting to delete the Shared Drive may still fail if there are untrashed items inside the Shared Drive .PARAMETER CanDownload Whether the current user can download files in this Shared Drive .PARAMETER CanEdit Whether the current user can edit files in this Shared Drive .PARAMETER CanListChildren Whether the current user can list the children of folders in this Shared Drive .PARAMETER CanManageMembers Whether the current user can add members to this Shared Drive or remove them or change their role .PARAMETER CanReadRevisions Whether the current user can read the revisions resource of files in this Shared Drive .PARAMETER CanRemoveChildren Whether the current user can remove children from folders in this Shared Drive .PARAMETER CanRename Whether the current user can rename files or folders in this Shared Drive .PARAMETER CanRenameDrive Whether the current user can rename this Shared Drive .PARAMETER CanShare Whether the current user can share files or folders in this Shared Drive .EXAMPLE Update-GSDrive -DriveId '0AJ8Xjq3FcdCKUk9PVA' -Name "HR Document Repo" Updated the Shared Drive with a new name, "HR Document Repo" #> [OutputType('Google.Apis.Drive.v3.Data.Drive')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0)] [Alias('Id','TeamDriveId')] [String] $DriveId, [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false)] [String] $Name, [parameter(Mandatory = $false)] [Switch] $CanAddChildren, [parameter(Mandatory = $false)] [Alias('CanChangeTeamDriveBackground')] [Switch] $CanChangeDriveBackground, [parameter(Mandatory = $false)] [Switch] $CanComment, [parameter(Mandatory = $false)] [Switch] $CanCopy, [parameter(Mandatory = $false)] [Alias('CanDeleteTeamDrive')] [Switch] $CanDeleteDrive, [parameter(Mandatory = $false)] [Switch] $CanDownload, [parameter(Mandatory = $false)] [Switch] $CanEdit, [parameter(Mandatory = $false)] [Switch] $CanListChildren, [parameter(Mandatory = $false)] [Switch] $CanManageMembers, [parameter(Mandatory = $false)] [Switch] $CanReadRevisions, [parameter(Mandatory = $false)] [Switch] $CanRemoveChildren, [parameter(Mandatory = $false)] [Switch] $CanRename, [parameter(Mandatory = $false)] [Alias('CanRenameTeamDrive')] [Switch] $CanRenameDrive, [parameter(Mandatory = $false)] [Switch] $CanShare ) 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.Drive.v3.DriveService' User = $User } $service = New-GoogleService @serviceParams try { $body = New-Object 'Google.Apis.Drive.v3.Data.Drive' $capabilities = New-Object 'Google.Apis.Drive.v3.Data.Drive+CapabilitiesData' foreach ($key in $PSBoundParameters.Keys) { switch ($key) { Default { if ($capabilities.PSObject.Properties.Name -contains $key) { $capabilities.$key = $PSBoundParameters[$key] } elseif ($body.PSObject.Properties.Name -contains $key) { $body.$key = $PSBoundParameters[$key] } } } } $body.Capabilities = $capabilities $request = $service.Drives.Update($body,$DriveId) Write-Verbose "Updating Shared Drive '$Name' for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Update-GSDrive' function Update-GSDriveFile { <# .SYNOPSIS Updates the metadata for a Drive file .DESCRIPTION Updates the metadata for a Drive file .PARAMETER FileId The unique Id of the Drive file to Update .PARAMETER Path The path to the local file whose content you would like to upload to Drive. .PARAMETER Name The name of the Drive file .PARAMETER Description The description of the Drive file .PARAMETER FolderColorRgb The color for a folder as an RGB hex string. Available values are: * "ChocolateIceCream" * "OldBrickRed" * "Cardinal" * "WildStrawberries" * "MarsOrange" * "YellowCab" * "Spearmint" * "VernFern" * "Asparagus" * "SlimeGreen" * "DesertSand" * "Macaroni" * "SeaFoam" * "Pool" * "Denim" * "RainySky" * "BlueVelvet" * "PurpleDino" * "Mouse" * "MountainGrey" * "Earthworm" * "BubbleGum" * "PurpleRain" * "ToyEggplant" .PARAMETER AddParents The parent Ids to add .PARAMETER RemoveParents The parent Ids to remove .PARAMETER CopyRequiresWriterPermission Whether the options to copy, print, or download this file, should be disabled for readers and commenters. .PARAMETER Starred Whether the user has starred the file. .PARAMETER Trashed Whether the file has been trashed, either explicitly or from a trashed parent folder. Only the owner may trash a file, and other users cannot see files in the owner's trash. .PARAMETER WritersCanShare If $true, sets Writers Can Share to true on the file. .PARAMETER Projection The defined subset of fields to be returned Available values are: * "Minimal" * "Standard" * "Full" * "Access" .PARAMETER Fields The specific fields to returned .PARAMETER User The email or unique Id of the Drive file owner .EXAMPLE Update-GSDriveFile -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976' -Name "To-Do Progress" Updates the Drive file with a new name, "To-Do Progress" .EXAMPLE Update-GSDriveFile -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976' -Path "C:\Pics\NewPic.png" Updates the Drive file with the content of the file at that path. In this example, the Drive file is a PNG named "Test.png". This will change the content of the file in Drive to match NewPic.png as well as rename it to "NewPic.png" #> [OutputType('Google.Apis.Drive.v3.Data.File')] [cmdletbinding(DefaultParameterSetName = "Depth")] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias('Id')] [string] $FileId, [parameter(Mandatory = $false,Position = 1)] [ValidateScript( { Test-Path $_ })] [string] $Path, [parameter(Mandatory = $false)] [string] $Name, [parameter(Mandatory = $false)] [String] $Description, [parameter(Mandatory = $false)] [ValidateSet('ChocolateIceCream','OldBrickRed','Cardinal','WildStrawberries','MarsOrange','YellowCab','Spearmint','VernFern','Asparagus','SlimeGreen','DesertSand','Macaroni','SeaFoam','Pool','Denim','RainySky','BlueVelvet','PurpleDino','Mouse','MountainGrey','Earthworm','BubbleGum','PurpleRain','ToyEggplant')] [string] $FolderColorRgb, [parameter(Mandatory = $false)] [string[]] $AddParents, [parameter(Mandatory = $false)] [String[]] $RemoveParents, [parameter(Mandatory = $false)] [switch] $CopyRequiresWriterPermission, [parameter(Mandatory = $false)] [switch] $Starred, [parameter(Mandatory = $false)] [switch] $Trashed, [parameter(Mandatory = $false)] [switch] $WritersCanShare, [parameter(Mandatory = $false,ParameterSetName = "Depth")] [Alias('Depth')] [ValidateSet("Minimal","Standard","Full","Access")] [String] $Projection = "Full", [parameter(Mandatory = $false,ParameterSetName = "Fields")] [ValidateSet("appProperties","capabilities","contentHints","createdTime","description","explicitlyTrashed","fileExtension","folderColorRgb","fullFileExtension","hasThumbnail","headRevisionId","iconLink","id","imageMediaMetadata","isAppAuthorized","kind","lastModifyingUser","md5Checksum","mimeType","modifiedByMe","modifiedByMeTime","modifiedTime","name","originalFilename","ownedByMe","owners","parents","permissions","properties","quotaBytesUsed","shared","sharedWithMeTime","sharingUser","size","spaces","starred","thumbnailLink","thumbnailVersion","trashed","version","videoMediaMetadata","viewedByMe","viewedByMeTime","viewersCanCopyContent","webContentLink","webViewLink","writersCanShare")] [String[]] $Fields, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail ) Begin { $colorDictionary = @{ ChocolateIceCream = '#ac725e' OldBrickRed = '#d06b64' Cardinal = '#f83a22' WildStrawberries = '#fa573c' MarsOrange = '#ff7537' YellowCab = '#ffad46' Spearmint = '#42d692' VernFern = '#16a765' Asparagus = '#7bd148' SlimeGreen = '#b3dc6c' DesertSand = '#fbe983' Macaroni = '#fad165' SeaFoam = '#92e1c0' Pool = '#9fe1e7' Denim = '#9fc6e7' RainySky = '#4986e7' BlueVelvet = '#9a9cff' PurpleDino = '#b99aff' Mouse = '#8f8f8f' MountainGrey = '#cabdbf' Earthworm = '#cca6ac' BubbleGum = '#f691b2' PurpleRain = '#cd74e6' ToyEggplant = '#a47ae2' } if ($PSCmdlet.ParameterSetName -eq 'Depth') { $fs = switch ($Projection) { Standard { @("createdTime","description","fileExtension","id","lastModifyingUser","modifiedTime","name","owners","parents","properties","version","webContentLink","webViewLink") } Access { @("createdTime","description","fileExtension","id","lastModifyingUser","modifiedTime","name","ownedByMe","owners","parents","permissionIds","permissions","shared","sharedWithMeTime","sharingUser","viewedByMe","viewedByMeTime","viewersCanCopyContent","writersCanShare") } Full { @("appProperties","capabilities","contentHints","createdTime","description","explicitlyTrashed","fileExtension","folderColorRgb","fullFileExtension","hasAugmentedPermissions","hasThumbnail","headRevisionId","iconLink","id","imageMediaMetadata","isAppAuthorized","kind","lastModifyingUser","md5Checksum","mimeType","modifiedByMe","modifiedByMeTime","modifiedTime","name","originalFilename","ownedByMe","owners","parents","permissionIds","permissions","properties","quotaBytesUsed","shared","sharedWithMeTime","sharingUser","size","spaces","starred","teamDriveId","thumbnailLink","thumbnailVersion","trashed","trashedTime","trashingUser","version","videoMediaMetadata","viewedByMe","viewedByMeTime","viewersCanCopyContent","webContentLink","webViewLink","writersCanShare") } } } elseif ($PSBoundParameters.ContainsKey('Fields')) { $fs = $Fields } } 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.Drive.v3.DriveService' User = $User } $service = New-GoogleService @serviceParams try { $body = New-Object 'Google.Apis.Drive.v3.Data.File' foreach ($prop in $PSBoundParameters.Keys | Where-Object { $body.PSObject.Properties.Name -contains $_ }) { switch ($prop) { Name { $body.Name = [String]$Name } FolderColorRgb { $body.FolderColorRgb = $ColorDictionary[$FolderColorRgb] } Default { $body.$prop = $PSBoundParameters[$prop] } } } if ($PSBoundParameters.Keys -contains 'Path') { $ioFile = Get-Item $Path $contentType = Get-MimeType $ioFile if ($PSBoundParameters.Keys -notcontains 'Name') { $body.Name = $ioFile.Name } $stream = New-Object 'System.IO.FileStream' $ioFile.FullName,'Open','Read' $request = $service.Files.Update($body,$FileId,$stream,$contentType) $request.QuotaUser = $User $request.ChunkSize = 512KB } else { $request = $service.Files.Update($body,$FileId) } $request.SupportsAllDrives = $true if ($fs) { $request.Fields = $($fs -join ",") } if ($AddParents) { $request.AddParents = $($AddParents -join ",") } if ($RemoveParents) { $request.RemoveParents = $($RemoveParents -join ",") } Write-Verbose "Updating file '$FileId' for user '$User'" if ($PSBoundParameters.Keys -contains 'Path') { $request.Upload() | Out-Null $stream.Close() } else { $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Update-GSDriveFile' function Update-GSDriveRevision { <# .SYNOPSIS Updates a revision with patch semantics .DESCRIPTION Updates a revision with patch semantics .PARAMETER FileId The unique Id of the file to update revisions for .PARAMETER RevisionId The unique Id of the revision to update .PARAMETER KeepForever Whether to keep this revision forever, even if it is no longer the head revision. If not set, the revision will be automatically purged 30 days after newer content is uploaded. This can be set on a maximum of 200 revisions for a file. This field is only applicable to files with binary content in Drive. .PARAMETER PublishAuto Whether subsequent revisions will be automatically republished. This is only applicable to Google Docs. .PARAMETER Published Whether this revision is published. This is only applicable to Google Docs. .PARAMETER PublishedOutsideDomain Whether this revision is published outside the domain. This is only applicable to Google Docs. .PARAMETER Fields The specific fields to returned .PARAMETER User The email or unique Id of the owner of the Drive file Defaults to the AdminEmail user .EXAMPLE Get-GSDriveRevision -FileId $fileId -Limit 1 | Update-GSDriveRevision -KeepForever Sets 'KeepForever' for the oldest revision of the file to 'True' .EXAMPLE Get-GSDriveRevision -FileId $fileId | Select-Object -Last 1 | Update-GSDriveRevision -KeepForever Sets 'KeepForever' for the newest revision of the file to 'True' #> [OutputType('Google.Apis.Drive.v3.Data.Revision')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] [String] $FileId, [parameter(Mandatory = $true,Position = 1,ValueFromPipelineByPropertyName = $true)] [Alias('Id')] [String[]] $RevisionId, [parameter(Mandatory = $false)] [Switch] $KeepForever, [parameter(Mandatory = $false)] [Switch] $PublishAuto, [parameter(Mandatory = $false)] [Switch] $Published, [parameter(Mandatory = $false)] [Switch] $PublishedOutsideDomain, [parameter(Mandatory = $false)] [String[]] $Fields = '*', [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail ) 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.Drive.v3.DriveService' User = $User } $service = New-GoogleService @serviceParams $body = New-Object 'Google.Apis.Drive.v3.Data.Revision' foreach ($key in $PSBoundParameters.Keys | Where-Object {$_ -in $body.PSObject.Properties.Name}) { $body.$key = $PSBoundParameters[$key] } foreach ($id in $RevisionId) { try { $request = $service.Revisions.Update($body,$FileId,$id) if ($Fields) { $request.Fields = $($Fields -join ",") } Write-Verbose "Updating Drive File Revision Id '$revision' of File Id '$FileId' for User '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru | Add-Member -MemberType NoteProperty -Name 'FileId' -Value $FileId -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Update-GSDriveRevision' function Watch-GSDriveUpload { <# .SYNOPSIS Shows progress in the console of current Drive file uploads .DESCRIPTION Shows progress in the console of current Drive file uploads .PARAMETER Id The upload Id(s) that you would like to watch .PARAMETER Action Whether the action is uploading or retrying. This is mainly for use in Start-GSDriveFileUpload and defaults to 'Uploading' .PARAMETER CountUploaded Current file count being uploaded .PARAMETER TotalUploading Total file count being uploaded .EXAMPLE Watch-GSDriveUpload Watches the files currently being uploaded from the active session #> [CmdletBinding()] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Int[]] $Id, [parameter(Mandatory = $false)] [ValidateSet('Uploading','Retrying')] [String] $Action = "Uploading", [parameter(Mandatory = $false)] [Int] $CountUploaded, [parameter(Mandatory = $false)] [Int] $TotalUploading ) Begin { Write-Verbose "Watching Drive File Upload" } Process { do { $i = 1 if ($PSBoundParameters.Keys -contains 'Id') { $statusList = Get-GSDriveFileUploadStatus -Id @($Id) } else { $statusList = Get-GSDriveFileUploadStatus } if ($statusList) { $totalPercent = 0 $totalSecondsRemaining = 0 $count = 0 $statusList | ForEach-Object { $count++ $totalPercent += $_.PercentComplete $totalSecondsRemaining += $_.Remaining.TotalSeconds } $curCount = if ($PSBoundParameters.Keys -contains 'CountUploaded') { $CountUploaded } else { $count } $totalCount = if ($PSBoundParameters.Keys -contains 'TotalUploading') { $TotalUploading } else { $count } $totalPercent = $totalPercent / $totalCount $totalSecondsRemaining = $totalSecondsRemaining / $totalCount $parentParams = @{ Activity = "[$([Math]::Round($totalPercent,4))%] $Action [$curCount / $totalCount] files to Google Drive" SecondsRemaining = $($statusList.Remaining.TotalSeconds | Sort-Object | Select-Object -Last 1) } if (!($statusList | Where-Object {$_.Status -ne "Completed"})) { $parentParams['Completed'] = $true } else { $parentParams['PercentComplete'] = [Math]::Round($totalPercent,4) } if ($psEditor -or $IsMacOS -or $IsLinux) { Write-InlineProgress @parentParams } else { $parentParams['Id'] = 1 Write-Progress @parentParams } if (!$psEditor -and !$IsMacOS -and !$IsLinux -and ($statusList.Count -le 5)) { foreach ($status in $statusList) { $i++ $statusFmt = if ($status.Status -eq "Completed") { "Completed uploading" } else { $status.Status } $progParams = @{ Activity = "[$($status.PercentComplete)%] [ID: $($status.Id)] $($statusFmt) file '$($status.File)' to Google Drive$(if($status.Parents){" (Parents: '$($status.Parents -join "', '")')"})" SecondsRemaining = $status.Remaining.TotalSeconds Id = $i ParentId = 1 } if ($_.Status -eq "Completed") { $progParams['Completed'] = $true } else { $progParams['PercentComplete'] = [Math]::Round($status.PercentComplete,4) } Write-Progress @progParams } } Start-Sleep -Seconds 1 } } until (!$statusList -or !($statusList | Where-Object {$_.Status -notin @("Failed","Completed")})) } } Export-ModuleMember -Function 'Watch-GSDriveUpload' function Add-GSGmailFilter { <# .SYNOPSIS Adds a new Gmail filter .DESCRIPTION Adds a new Gmail filter .PARAMETER User The email of the user you are adding the filter for .PARAMETER From The sender's display name or email address. .PARAMETER To The recipient's display name or email address. Includes recipients in the "to", "cc", and "bcc" header fields. You can use simply the local part of the email address. For example, "example" and "example@" both match "example@gmail.com". This field is case-insensitive .PARAMETER Subject Case-insensitive phrase found in the message's subject. Trailing and leading whitespace are be trimmed and adjacent spaces are collapsed .PARAMETER Query Only return messages matching the specified query. Supports the same query format as the Gmail search box. For example, "from:someuser@example.com rfc822msgid: is:unread" .PARAMETER NegatedQuery Only return messages not matching the specified query. Supports the same query format as the Gmail search box. For example, "from:someuser@example.com rfc822msgid: is:unread" .PARAMETER HasAttachment Whether the message has any attachment .PARAMETER ExcludeChats Whether the response should exclude chats .PARAMETER AddLabelIDs List of labels to add to the message .PARAMETER RemoveLabelIDs List of labels to remove from the message .PARAMETER Forward Email address that the message should be forwarded to .PARAMETER Size The size of the entire RFC822 message in bytes, including all headers and attachments .PARAMETER SizeComparison How the message size in bytes should be in relation to the size field. Acceptable values are: * "larger" * "smaller" * "unspecified" .PARAMETER Raw If $true, returns the raw response. If not passed or -Raw:$false, response is formatted as a flat object for readability .EXAMPLE Add-GSGmailFilter -To admin@domain.com -ExcludeChats -Forward "admin_directMail@domain.com" Adds a filter for the AdminEmail user to forward all mail sent directly to the to "admin_directMail@domain.com" #> [OutputType('Google.Apis.Gmail.v1.Data.Filter')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [string] $From, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [string] $To, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [string] $Subject, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [string] $Query, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [string] $NegatedQuery, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Switch] $HasAttachment, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Switch] $ExcludeChats, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [string[]] $AddLabelIDs, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [string[]] $RemoveLabelIDs, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [string] $Forward, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [int] $Size, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [ValidateSet('Larger','Smaller','Unspecified')] [string] $SizeComparison, [parameter(Mandatory = $false)] [Switch] $Raw ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/gmail.settings.basic' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams try { $body = New-Object 'Google.Apis.Gmail.v1.Data.Filter' $action = New-Object 'Google.Apis.Gmail.v1.Data.FilterAction' $criteria = New-Object 'Google.Apis.Gmail.v1.Data.FilterCriteria' foreach ($key in $PSBoundParameters.Keys) { switch ($key) { AddLabelIDs { $action.$key = [String[]]($PSBoundParameters[$key]) } RemoveLabelIds { $action.$key = [String[]]($PSBoundParameters[$key]) } Forward { $action.$key = $PSBoundParameters[$key] } Default { if ($criteria.PSObject.Properties.Name -contains $key) { $criteria.$key = $PSBoundParameters[$key] } } } } $body.Action = $action $body.Criteria = $criteria $request = $service.Users.Settings.Filters.Create($body,$User) Write-Verbose "Creating Filter for user '$User'" $response = $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru if (!$Raw) { $response = $response | Select-Object User,Id,@{N = "From";E = {$_.criteria.from}},@{N = "To";E = {$_.criteria.to}},@{N = "Subject";E = {$_.criteria.subject}},@{N = "Query";E = {$_.criteria.query}},@{N = "NegatedQuery";E = {$_.criteria.negatedQuery}},@{N = "HasAttachment";E = {$_.criteria.hasAttachment}},@{N = "ExcludeChats";E = {$_.criteria.excludeChats}},@{N = "Size";E = {$_.criteria.size}},@{N = "SizeComparison";E = {$_.criteria.sizeComparison}},@{N = "AddLabelIds";E = {$_.action.addLabelIds}},@{N = "RemoveLabelIds";E = {$_.action.removeLabelIds}},@{N = "Forward";E = {$_.action.forward}} } $response } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Add-GSGmailFilter' function Add-GSGmailForwardingAddress { <# .SYNOPSIS Creates a forwarding address. .DESCRIPTION Creates a forwarding address. If ownership verification is required, a message will be sent to the recipient and the resource's verification status will be set to pending; otherwise, the resource will be created with verification status set to accepted. .PARAMETER ForwardingAddress An email address to which messages can be forwarded. .PARAMETER User The user to create the forwarding addresses for Defaults to the AdminEmail user .EXAMPLE Add-GSGmailForwardingAddress "joe@domain.com" Adds joe@domain.com as a forwarding address for the AdminEmail user #> [OutputType('Google.Apis.Gmail.v1.Data.ForwardingAddress')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("Id")] [string[]] $ForwardingAddress, [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [string] $User = $Script:PSGSuite.AdminEmail ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/gmail.settings.sharing' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams try { foreach ($fwd in $ForwardingAddress) { $body = New-Object 'Google.Apis.Gmail.v1.Data.ForwardingAddress' -Property @{ ForwardingEmail = $fwd } $request = $service.Users.Settings.ForwardingAddresses.Create($body,$User) Write-Verbose "Creating Forwarding Address '$fwd' for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Add-GSGmailForwardingAddress' function Get-GSGmailAutoForwardingSettings { <# .SYNOPSIS Gets AutoForwarding settings .DESCRIPTION Gets AutoForwarding settings .PARAMETER User The user to get the AutoForwarding settings for Defaults to the AdminEmail user .EXAMPLE Get-GSGmailAutoForwardingSettings Gets the AutoForwarding settings for the AdminEmail user #> [OutputType('Google.Apis.Gmail.v1.Data.AutoForwarding')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [string] $User = $Script:PSGSuite.AdminEmail ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/gmail.settings.basic' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams try { $request = $service.Users.Settings.GetAutoForwarding($User) Write-Verbose "Getting AutoForwarding settings for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSGmailAutoForwardingSettings' function Get-GSGmailFilter { <# .SYNOPSIS Gets Gmail filter details .DESCRIPTION Gets Gmail filter details .PARAMETER FilterId The unique Id of the filter you would like to retrieve information for. If excluded, all filters for the user are returned .PARAMETER User The email of the user you are getting the filter information for .PARAMETER Raw If $true, returns the raw response. If not passed or -Raw:$false, response is formatted as a flat object for readability .EXAMPLE Get-GSGmailFilter -User joe Gets the list of filters for Joe #> [OutputType('Google.Apis.Gmail.v1.Data.Filter')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias("Id")] [string[]] $FilterId, [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false)] [switch] $Raw ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/gmail.settings.basic' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams try { if ($FilterId) { foreach ($fil in $FilterId) { $request = $service.Users.Settings.Filters.Get($User,$fil) Write-Verbose "Getting Filter Id '$fil' for user '$User'" $response = $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru if (!$Raw) { $response = $response | Select-Object User,Id,@{N = "From";E = {$_.criteria.from}},@{N = "To";E = {$_.criteria.to}},@{N = "Subject";E = {$_.criteria.subject}},@{N = "Query";E = {$_.criteria.query}},@{N = "NegatedQuery";E = {$_.criteria.negatedQuery}},@{N = "HasAttachment";E = {$_.criteria.hasAttachment}},@{N = "ExcludeChats";E = {$_.criteria.excludeChats}},@{N = "Size";E = {$_.criteria.size}},@{N = "SizeComparison";E = {$_.criteria.sizeComparison}},@{N = "AddLabelIds";E = {$_.action.addLabelIds}},@{N = "RemoveLabelIds";E = {$_.action.removeLabelIds}},@{N = "Forward";E = {$_.action.forward}} } $response } } else { $request = $service.Users.Settings.Filters.List($User) Write-Verbose "Getting Filter List for user '$User'" $response = $request.Execute() | Select-Object -ExpandProperty Filter | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru if (!$Raw) { $response = $response | Select-Object User,Id,@{N = "From";E = {$_.criteria.from}},@{N = "To";E = {$_.criteria.to}},@{N = "Subject";E = {$_.criteria.subject}},@{N = "Query";E = {$_.criteria.query}},@{N = "NegatedQuery";E = {$_.criteria.negatedQuery}},@{N = "HasAttachment";E = {$_.criteria.hasAttachment}},@{N = "ExcludeChats";E = {$_.criteria.excludeChats}},@{N = "Size";E = {$_.criteria.size}},@{N = "SizeComparison";E = {$_.criteria.sizeComparison}},@{N = "AddLabelIds";E = {$_.action.addLabelIds}},@{N = "RemoveLabelIds";E = {$_.action.removeLabelIds}},@{N = "Forward";E = {$_.action.forward}} } $response } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSGmailFilter' function Get-GSGmailForwardingAddress { <# .SYNOPSIS Gets Gmail forwarding address information for the user .DESCRIPTION Gets Gmail forwarding address information for the user .PARAMETER ForwardingAddress The forwarding address you would like to get info for. If excluded, gets the list of forwarding addresses and their info for the user .PARAMETER User The user to get the forwarding addresses for Defaults to the AdminEmail user .EXAMPLE Get-GSGmailForwardingAddress Gets the list of forwarding addresses for the AdminEmail user #> [OutputType('Google.Apis.Gmail.v1.Data.ForwardingAddress')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias("Id")] [string[]] $ForwardingAddress, [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [string] $User = $Script:PSGSuite.AdminEmail ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/gmail.settings.basic' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams try { if ($ForwardingAddress) { foreach ($fwd in $ForwardingAddress) { $request = $service.Users.Settings.ForwardingAddresses.Get($User,$fwd) Write-Verbose "Getting Forwarding Address '$fwd' for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } } else { $request = $service.Users.Settings.ForwardingAddresses.List($User) Write-Verbose "Getting Forwarding Address List for user '$User'" $request.Execute() | Select-Object -ExpandProperty ForwardingAddresses | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSGmailForwardingAddress' function Get-GSGmailImapSettings { <# .SYNOPSIS Gets IMAP settings .DESCRIPTION Gets IMAP settings .PARAMETER User The user to get the IMAP settings for Defaults to the AdminEmail user .EXAMPLE Get-GSGmailImapSettings Gets the IMAP settings for the AdminEmail user #> [OutputType('Google.Apis.Gmail.v1.Data.ImapSettings')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [string] $User = $Script:PSGSuite.AdminEmail ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/gmail.settings.basic' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams try { $request = $service.Users.Settings.GetImap($User) Write-Verbose "Getting IMAP settings for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSGmailImapSettings' function Get-GSGmailLabel { <# .SYNOPSIS Gets Gmail label information for the user .DESCRIPTION Gets Gmail label information for the user .PARAMETER LabelId The unique Id of the label to get information for. If excluded, returns the list of labels for the user .PARAMETER User The user to get label information for Defaults to the AdminEmail user .EXAMPLE Get-GSGmailLabel Gets the Gmail labels of the AdminEmail user #> [OutputType('Google.Apis.Gmail.v1.Data.Label')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias("Id")] [string[]] $LabelId, [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [string] $User = $Script:PSGSuite.AdminEmail ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://mail.google.com' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams try { if ($LabelId) { foreach ($label in $LabelId) { $request = $service.Users.Labels.Get($User,$label) Write-Verbose "Getting Label Id '$label' for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } } else { $request = $service.Users.Labels.List($User) Write-Verbose "Getting Label List for user '$User'" $request.Execute() | Select-Object -ExpandProperty Labels | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSGmailLabel' function Get-GSGmailLanguageSettings { <# .SYNOPSIS Gets Gmail display language settings .DESCRIPTION Gets Gmail display language settings .PARAMETER User The user to get the Gmail display language settings for. Defaults to the AdminEmail user. .EXAMPLE Get-GSGmailLanguageSettings -User me Gets the Gmail display language for the AdminEmail user. #> [OutputType('Google.Apis.Gmail.v1.Data.LanguageSettings')] [cmdletbinding()] Param ( [parameter(Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [string[]] $User = $Script:PSGSuite.AdminEmail ) Process { foreach ($U in $User) { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/gmail.settings.basic' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $U } $service = New-GoogleService @serviceParams try { $request = $service.Users.Settings.GetLanguage($U) Write-Verbose "Getting Gmail Display Language for user '$U'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Get-GSGmailLanguageSettings' function Get-GSGmailMessage { <# .SYNOPSIS Gets Gmail message details .DESCRIPTION Gets Gmail message details .PARAMETER User The primary email of the user who owns the message Defaults to the AdminEmail user .PARAMETER Id The Id of the message to retrieve info for .PARAMETER ParseMessage If $true, returns the parsed raw message .PARAMETER SaveAttachmentsTo If the message has attachments, the path to save the attachments to. If excluded, attachments are not saved locally .PARAMETER Format The format of the message metadata to retrieve Available values are: * "Full" * "Metadata" * "Minimal" * "Raw" Defaults to "Full", but forces -Format as "Raw" if -ParseMessage or -SaveAttachmentsTo are used .EXAMPLE Get-GSGmailMessage -Id 1615f9a6ee36cb5b -ParseMessage Gets the full message details for the provided Id and parses out the raw MIME message content #> [OutputType('Google.Apis.Gmail.v1.Data.Message')] [cmdletbinding(DefaultParameterSetName = "Format")] Param ( [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [Alias('MessageId')] [String[]] $Id, [parameter(Mandatory = $false,ParameterSetName = "ParseMessage")] [switch] $ParseMessage, [parameter(Mandatory = $false,ParameterSetName = "ParseMessage")] [Alias('AttachmentOutputPath','OutFilePath')] [ValidateScript({(Get-Item $_).PSIsContainer})] [string] $SaveAttachmentsTo, [parameter(Mandatory = $false,ParameterSetName = "Format")] [ValidateSet("Full","Metadata","Minimal","Raw")] [string] $Format = "Full" ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } if ($ParseMessage -or $SaveAttachmentsTo) { $Format = "Raw" } $serviceParams = @{ Scope = 'https://mail.google.com' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams try { foreach ($mId in $Id) { $request = $service.Users.Messages.Get($User,$mId) $request.Format = $Format foreach ($key in $PSBoundParameters.Keys | Where-Object {$_ -ne "Id"}) { switch ($key) { Default { if ($request.PSObject.Properties.Name -contains $key) { $request.$key = $PSBoundParameters[$key] } } } } Write-Verbose "Getting Message Id '$mId' for user '$User'" $result = $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru if ($ParseMessage -or $SaveAttachmentsTo) { $parsed = Read-MimeMessage -String $(Convert-Base64 -From WebSafeBase64String -To NormalString -String $result.Raw) | Select-Object @{N = 'User';E = {$User}},@{N = "Id";E = {$result.Id}},@{N = "ThreadId";E = {$result.ThreadId}},@{N = "LabelIds";E = {$result.LabelIds}},@{N = "Snippet";E = {$result.Snippet}},@{N = "HistoryId";E = {$result.HistoryId}},@{N = "InternalDate";E = {$result.InternalDate}},@{N = "InternalDateConverted";E = {Convert-EpochToDate -EpochString $result.internalDate}},@{N = "SizeEstimate";E = {$result.SizeEstimate}},* if ($SaveAttachmentsTo) { $resPath = Resolve-Path $SaveAttachmentsTo $attachments = New-Object System.Collections.Generic.List[object] if ($parsed.Attachments | Where-Object {$_.FileName}) { $parsed.Attachments | Where-Object {$_.FileName} | ForEach-Object { $attachments.Add($_) } } if ($parsed.BodyParts | Where-Object {$_.FileName}) { $parsed.BodyParts | Where-Object {$_.FileName} | ForEach-Object { $attachments.Add($_) } } $attachments = $attachments | Sort-Object {"$($_.FileName)_$($_.ContentId)"} -Unique foreach ($att in $attachments) { $cleanedName = Get-SafeFileName $att.FileName $fileName = Join-Path $resPath $cleanedName Write-Verbose "Saving attachment to path '$fileName'" $stream = [System.IO.File]::Create($fileName) $att.ContentObject.DecodeTo($stream) $stream.Close() } } $parsed } else { $result } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSGmailMessage' function Get-GSGmailMessageList { <# .SYNOPSIS Gets a list of messages .DESCRIPTION Gets a list of messages .PARAMETER User The primary email of the user to list messages for Defaults to the AdminEmail user .PARAMETER Filter Only return messages matching the specified query. Supports the same query format as the Gmail search box. For example, "from:someuser@example.com rfc822msgid:<lkj123l4jj1lj@gmail.com> is:unread" More info on Gmail search operators here: https://support.google.com/mail/answer/7190?hl=en .PARAMETER Rfc822MsgId The RFC822 Message ID to add to your filter. .PARAMETER LabelIds Only return messages with labels that match all of the specified label IDs .PARAMETER ExcludeChats Exclude chats from the message list .PARAMETER IncludeSpamTrash Include messages from SPAM and TRASH in the results .PARAMETER PageSize The page size of the result set .PARAMETER Limit The maximum amount of results you want returned. Exclude or set to 0 to return all results .EXAMPLE Get-GSGmailMessageList -Filter "to:me","after:2017/12/25" -ExcludeChats Gets the list of messages sent directly to the user after 2017/12/25 excluding chats #> [OutputType('Google.Apis.Gmail.v1.Data.Message')] [cmdletbinding(DefaultParameterSetName = "Filter")] Param ( [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [String[]] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false,ParameterSetName = "Filter")] [Alias('Query')] [String[]] $Filter, [parameter(Mandatory = $false,ParameterSetName = "Rfc822MsgId")] [Alias('MessageId','MsgId')] [String] $Rfc822MsgId, [parameter(Mandatory = $false)] [Alias('LabelId')] [String[]] $LabelIds, [parameter(Mandatory = $false)] [switch] $ExcludeChats, [parameter(Mandatory = $false)] [switch] $IncludeSpamTrash, [parameter(Mandatory = $false)] [ValidateRange(1,500)] [Int] $PageSize = 500, [parameter(Mandatory = $false)] [Alias('First')] [Int] $Limit = 0 ) Process { try { foreach ($U in $User) { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://mail.google.com' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $U } if ($ExcludeChats) { if ($Filter) { $Filter += "-in:chats" } else { $Filter = "-in:chats" } } $service = New-GoogleService @serviceParams $request = $service.Users.Messages.List($U) foreach ($key in $PSBoundParameters.Keys) { switch ($key) { Rfc822MsgId { $request.Q = "rfc822msgid:$($Rfc822MsgId.Trim())" } Filter { $request.Q = $($Filter -join " ") } LabelIds { $request.LabelIds = [String[]]$LabelIds } Default { if ($request.PSObject.Properties.Name -contains $key) { $request.$key = $PSBoundParameters[$key] } } } } 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) $PageSize = $Limit } $request.MaxResults = $PageSize if ($request.Q) { Write-Verbose "Getting all Messages matching filter '$($request.Q)' for user '$U'" } else { Write-Verbose "Getting all Messages for user '$U'" } [int]$i = 1 $overLimit = $false do { $result = $request.Execute() if ($result.Messages) { $result.Messages | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru | Add-Member -MemberType NoteProperty -Name 'Filter' -Value $Filter -PassThru } if ($result.NextPageToken) { $request.PageToken = $result.NextPageToken } [int]$retrieved = ($i + $result.Messages.Count) - 1 Write-Verbose "Retrieved $retrieved Messages..." if ($Limit -gt 0 -and $retrieved -eq $Limit) { Write-Verbose "Limit reached: $Limit" $overLimit = $true } elseif ($Limit -gt 0 -and ($retrieved + $PageSize) -gt $Limit) { $newPS = $Limit - $retrieved Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize,$newPS) $request.MaxResults = $newPS } [int]$i = $i + $result.Messages.Count } until ($overLimit -or !$result.NextPageToken) } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSGmailMessageList' function Get-GSGmailPopSettings { <# .SYNOPSIS Gets POP settings .DESCRIPTION Gets POP settings .PARAMETER User The user to get the POP settings for Defaults to the AdminEmail user .EXAMPLE Get-GSGmailPopSettings Gets the POP settings for the AdminEmail user #> [OutputType('Google.Apis.Gmail.v1.Data.PopSettings')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [string] $User = $Script:PSGSuite.AdminEmail ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/gmail.settings.basic' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams try { $request = $service.Users.Settings.GetPop($User) Write-Verbose "Getting POP settings for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSGmailPopSettings' function Get-GSGmailProfile { <# .SYNOPSIS Gets Gmail profile for the user .DESCRIPTION Gets Gmail profile for the user .PARAMETER User The user to get profile of Defaults to the AdminEmail user .EXAMPLE Get-GSGmailProfile Gets the Gmail profile of the AdminEmail user #> [OutputType('Google.Apis.Gmail.v1.Data.Profile')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String[]] $User = $Script:PSGSuite.AdminEmail ) Process { foreach ($U in $User) { try { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://mail.google.com' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $U } $service = New-GoogleService @serviceParams $request = $service.Users.GetProfile($U) Write-Verbose "Getting Gmail profile for user '$U'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Get-GSGmailProfile' function Get-GSGmailSendAsAlias { <# .SYNOPSIS Gets SendAs alias settings for a user. .DESCRIPTION Gets SendAs alias settings for a user. .PARAMETER SendAsEmail The SendAs alias to be retrieved. If excluded, gets the list of SendAs aliases. .PARAMETER User The email of the user you are getting the information for .EXAMPLE Get-GSGmailSendAsSettings -User joe@domain.com Gets the list of SendAs Settings for Joe #> [OutputType('Google.Apis.Gmail.v1.Data.SendAs')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("SendAs")] [string[]] $SendAsEmail, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [string] $User = $Script:PSGSuite.AdminEmail ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/gmail.settings.basic' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams if ($PSBoundParameters.ContainsKey('SendAsEmail')) { foreach ($sendAs in $SendAsEmail) { try { if ($sendAs -notlike "*@*.*") { $sendAs = "$($sendAs)@$($Script:PSGSuite.Domain)" } $request = $service.Users.Settings.SendAs.Get($User,$sendAs) Write-Verbose "Getting SendAs settings of alias '$sendAs' for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } else { try { $request = $service.Users.Settings.SendAs.List($User) Write-Verbose "Getting SendAs List for user '$User'" $request.Execute() | Select-Object -ExpandProperty SendAs | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Get-GSGmailSendAsAlias' function Get-GSGmailSMIMEInfo { <# .SYNOPSIS Gets Gmail S/MIME info .DESCRIPTION Gets Gmail S/MIME info .PARAMETER SendAsEmail The email address that appears in the "From:" header for mail sent using this alias. .PARAMETER Id The immutable ID for the SmimeInfo. If left blank, returns the list of S/MIME infos for the SendAsEmail and User .PARAMETER User The user's email address Defaults to the AdminEmail user .EXAMPLE Get-GSGmailSMIMEInfo -SendAsEmail 'joe@otherdomain.com' -User joe@domain.com Gets the list of S/MIME infos for Joe's SendAsEmail 'joe@otherdomain.com' #> [OutputType('Google.Apis.Gmail.v1.Data.SmimeInfo')] [cmdletbinding()] Param ( [parameter(Mandatory = $true)] [string] $SendAsEmail, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [string[]] $Id, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [string] $User = $Script:PSGSuite.AdminEmail ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/gmail.settings.basic' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams try { if ($PSBoundParameters.Keys -contains 'Id') { foreach ($I in $Id) { $request = $service.Users.Settings.SendAs.SmimeInfo.Get($User,$SendAsEmail,$I) Write-Verbose "Getting S/MIME Id '$I' of SendAsEmail '$SendAsEmail' for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } } else { $request = $service.Users.Settings.SendAs.SmimeInfo.List($User,$SendAsEmail) Write-Verbose "Getting list of S/MIME Id's of SendAsEmail '$SendAsEmail' for user '$User'" $request.Execute() | Select-Object -ExpandProperty smimeInfo | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSGmailSMIMEInfo' function Get-GSGmailVacationSettings { <# .SYNOPSIS Gets Vacation settings .DESCRIPTION Gets Vacation settings .PARAMETER User The user to get the Vacation settings for Defaults to the AdminEmail user .EXAMPLE Get-GSGmailVacationSettings Gets the Vacation settings for the AdminEmail user #> [OutputType('Google.Apis.Gmail.v1.Data.VacationSettings')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [string] $User = $Script:PSGSuite.AdminEmail ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/gmail.settings.basic' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams try { $request = $service.Users.Settings.GetVacation($User) Write-Verbose "Getting Vacation settings for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSGmailVacationSettings' function New-GSGmailLabel { <# .SYNOPSIS Adds a Gmail label .DESCRIPTION Adds a Gmail label .PARAMETER Name The name of the label to add .PARAMETER LabelListVisibility The visibility of the label in the label list in the Gmail web interface. Acceptable values are: * "labelHide": Do not show the label in the label list. * "labelShow": Show the label in the label list. (Default) * "labelShowIfUnread": Show the label if there are any unread messages with that label. .PARAMETER MessageListVisibility The visibility of messages with this label in the message list in the Gmail web interface. Acceptable values are: * "hide": Do not show the label in the message list. * "show": Show the label in the message list. (Default) .PARAMETER BackgroundColor The background color of the label Options and their corresponding hex code: Amethyst = '#8e63ce' BananaMania = '#fce8b3' Bermuda = '#68dfa9' BilobaFlower = '#b694e8' Black = '#000000' BlueRomance = '#c6f3de' BrandyPunch = '#cf8933' BurntSienna = '#e66550' Cadillac = '#b65775' Camelot = '#83334c' CeruleanBlue = '#285bac' ChathamsBlue = '#1c4587' Concrete = '#f3f3f3' CornflowerBlue = '#4a86e8' LightCornflowerBlue = '#6d9eeb' CreamCan = '#f2c960' Cupid = '#fbc8d9' DeepBlush = '#e07798' Desert = '#a46a21' DoveGray = '#666666' DustyGray = '#999999' Eucalyptus = '#2a9c68' Flesh = '#ffd6a2' FringyFlower = '#b9e4d0' Gallery = '#efefef' Goldenrod = '#fad165' Illusion = '#f7a7c0' Jewel = '#1a764d' Koromiko = '#ffbc6b' LightMountainMeadow = '#16a766' LightShamrock = '#43d692' LuxorGold = '#aa8831' MandysPink = '#f6c5be' MediumPurple = '#a479e2' Meteorite = '#41236d' MoonRaker = '#d0bcf1' LightMoonRaker = '#e4d7f5' MountainMeadow = '#149e60' Oasis = '#fef1d1' OceanGreen = '#44b984' OldGold = '#d5ae49' Perano = '#a4c2f4' PersianPink = '#f691b3' PigPink = '#fcdee8' Pueblo = '#822111' RedOrange = '#fb4c2f' RoyalBlue = '#3c78d8' RoyalPurple = '#653e9b' Salem = '#0b804b' Salomie = '#fcda83' SeaPink = '#efa093' Shamrock = '#3dc789' Silver = '#cccccc' Tabasco = '#ac2b16' Tequila = '#ffe6c7' Thunderbird = '#cc3a21' TropicalBlue = '#c9daf8' TulipTree = '#eaa041' Tundora = '#434343' VistaBlue = '#89d3b2' Watercourse = '#076239' WaterLeaf = '#a0eac9' White = '#ffffff' YellowOrange = '#ffad47' .PARAMETER TextColor The text color of the label Options and their corresponding hex code: Amethyst = '#8e63ce' BananaMania = '#fce8b3' Bermuda = '#68dfa9' BilobaFlower = '#b694e8' Black = '#000000' BlueRomance = '#c6f3de' BrandyPunch = '#cf8933' BurntSienna = '#e66550' Cadillac = '#b65775' Camelot = '#83334c' CeruleanBlue = '#285bac' ChathamsBlue = '#1c4587' Concrete = '#f3f3f3' CornflowerBlue = '#4a86e8' LightCornflowerBlue = '#6d9eeb' CreamCan = '#f2c960' Cupid = '#fbc8d9' DeepBlush = '#e07798' Desert = '#a46a21' DoveGray = '#666666' DustyGray = '#999999' Eucalyptus = '#2a9c68' Flesh = '#ffd6a2' FringyFlower = '#b9e4d0' Gallery = '#efefef' Goldenrod = '#fad165' Illusion = '#f7a7c0' Jewel = '#1a764d' Koromiko = '#ffbc6b' LightMountainMeadow = '#16a766' LightShamrock = '#43d692' LuxorGold = '#aa8831' MandysPink = '#f6c5be' MediumPurple = '#a479e2' Meteorite = '#41236d' MoonRaker = '#d0bcf1' LightMoonRaker = '#e4d7f5' MountainMeadow = '#149e60' Oasis = '#fef1d1' OceanGreen = '#44b984' OldGold = '#d5ae49' Perano = '#a4c2f4' PersianPink = '#f691b3' PigPink = '#fcdee8' Pueblo = '#822111' RedOrange = '#fb4c2f' RoyalBlue = '#3c78d8' RoyalPurple = '#653e9b' Salem = '#0b804b' Salomie = '#fcda83' SeaPink = '#efa093' Shamrock = '#3dc789' Silver = '#cccccc' Tabasco = '#ac2b16' Tequila = '#ffe6c7' Thunderbird = '#cc3a21' TropicalBlue = '#c9daf8' TulipTree = '#eaa041' Tundora = '#434343' VistaBlue = '#89d3b2' Watercourse = '#076239' WaterLeaf = '#a0eac9' White = '#ffffff' YellowOrange = '#ffad47' .PARAMETER User The primary email of the user to add the label to Defaults to the AdminEmail user .EXAMPLE New-GSGmailLabel -Name Label1 Adds the label "Label1" to the AdminEmail #> [OutputType('Google.Apis.Gmail.v1.Data.Label')] [cmdletbinding()] Param ( [parameter(Mandatory = $true, Position = 0)] [string] $Name, [parameter(Mandatory = $false)] [ValidateSet('labelHide','labelShow','labelShowIfUnread')] [string] $LabelListVisibility = "labelShow", [parameter(Mandatory = $false)] [ValidateSet('show','hide')] [string] $MessageListVisibility = "show", [parameter(Mandatory = $false)] [ValidateSet('Amethyst','BananaMania','Bermuda','BilobaFlower','Black','BlueRomance','BrandyPunch','BurntSienna','Cadillac','Camelot','CeruleanBlue','ChathamsBlue','Concrete','CornflowerBlue','CreamCan','Cupid','DeepBlush','Desert','DoveGray','DustyGray','Eucalyptus','Flesh','FringyFlower','Gallery','Goldenrod','Illusion','Jewel','Koromiko','LightCornflowerBlue','LightMoonRaker','LightMountainMeadow','LightShamrock','LuxorGold','MandysPink','MediumPurple','Meteorite','MoonRaker','MountainMeadow','Oasis','OceanGreen','OldGold','Perano','PersianPink','PigPink','Pueblo','RedOrange','RoyalBlue','RoyalPurple','Salem','Salomie','SeaPink','Shamrock','Silver','Tabasco','Tequila','Thunderbird','TropicalBlue','TulipTree','Tundora','VistaBlue','Watercourse','WaterLeaf','White','YellowOrange')] [string] $BackgroundColor, [parameter(Mandatory = $false)] [ValidateSet('Amethyst','BananaMania','Bermuda','BilobaFlower','Black','BlueRomance','BrandyPunch','BurntSienna','Cadillac','Camelot','CeruleanBlue','ChathamsBlue','Concrete','CornflowerBlue','CreamCan','Cupid','DeepBlush','Desert','DoveGray','DustyGray','Eucalyptus','Flesh','FringyFlower','Gallery','Goldenrod','Illusion','Jewel','Koromiko','LightCornflowerBlue','LightMoonRaker','LightMountainMeadow','LightShamrock','LuxorGold','MandysPink','MediumPurple','Meteorite','MoonRaker','MountainMeadow','Oasis','OceanGreen','OldGold','Perano','PersianPink','PigPink','Pueblo','RedOrange','RoyalBlue','RoyalPurple','Salem','Salomie','SeaPink','Shamrock','Silver','Tabasco','Tequila','Thunderbird','TropicalBlue','TulipTree','Tundora','VistaBlue','Watercourse','WaterLeaf','White','YellowOrange')] [string] $TextColor, [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail", "UserKey", "Mail")] [ValidateNotNullOrEmpty()] [string] $User = $Script:PSGSuite.AdminEmail ) Begin { $colorDict = @{ Amethyst = '#8e63ce' BananaMania = '#fce8b3' Bermuda = '#68dfa9' BilobaFlower = '#b694e8' Black = '#000000' BlueRomance = '#c6f3de' BrandyPunch = '#cf8933' BurntSienna = '#e66550' Cadillac = '#b65775' Camelot = '#83334c' CeruleanBlue = '#285bac' ChathamsBlue = '#1c4587' Concrete = '#f3f3f3' CornflowerBlue = '#4a86e8' LightCornflowerBlue = '#6d9eeb' CreamCan = '#f2c960' Cupid = '#fbc8d9' DeepBlush = '#e07798' Desert = '#a46a21' DoveGray = '#666666' DustyGray = '#999999' Eucalyptus = '#2a9c68' Flesh = '#ffd6a2' FringyFlower = '#b9e4d0' Gallery = '#efefef' Goldenrod = '#fad165' Illusion = '#f7a7c0' Jewel = '#1a764d' Koromiko = '#ffbc6b' LightMountainMeadow = '#16a766' LightShamrock = '#43d692' LuxorGold = '#aa8831' MandysPink = '#f6c5be' MediumPurple = '#a479e2' Meteorite = '#41236d' MoonRaker = '#d0bcf1' LightMoonRaker = '#e4d7f5' MountainMeadow = '#149e60' Oasis = '#fef1d1' OceanGreen = '#44b984' OldGold = '#d5ae49' Perano = '#a4c2f4' PersianPink = '#f691b3' PigPink = '#fcdee8' Pueblo = '#822111' RedOrange = '#fb4c2f' RoyalBlue = '#3c78d8' RoyalPurple = '#653e9b' Salem = '#0b804b' Salomie = '#fcda83' SeaPink = '#efa093' Shamrock = '#3dc789' Silver = '#cccccc' Tabasco = '#ac2b16' Tequila = '#ffe6c7' Thunderbird = '#cc3a21' TropicalBlue = '#c9daf8' TulipTree = '#eaa041' Tundora = '#434343' VistaBlue = '#89d3b2' Watercourse = '#076239' WaterLeaf = '#a0eac9' White = '#ffffff' YellowOrange = '#ffad47' } } Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://mail.google.com' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams $body = New-Object 'Google.Apis.Gmail.v1.Data.Label' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) { $body.$prop = $PSBoundParameters[$prop] } if ($PSBoundParameters.Keys -contains 'BackgroundColor' -or $PSBoundParameters.Keys -contains 'TextColor') { $color = New-Object 'Google.Apis.Gmail.v1.Data.LabelColor' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$color.PSObject.Properties.Name -contains $_}) { $color.$prop = $colorDict[$PSBoundParameters[$prop]] } $body.Color = $color } try { Write-Verbose "Creating Label '$Name' for user '$User'" $request = $service.Users.Labels.Create($body, $User) $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'New-GSGmailLabel' function New-GSGmailSendAsAlias { <# .SYNOPSIS Creates a new SendAs alias for a user. .DESCRIPTION Creates a new SendAs alias for a user. .PARAMETER User The user to create the SendAs alias for. .PARAMETER SendAsEmail The email address that appears in the "From:" header for mail sent using this alias. .PARAMETER DisplayName A name that appears in the "From:" header for mail sent using this alias. For custom "from" addresses, when this is empty, Gmail will populate the "From:" header with the name that is used for the primary address associated with the account. If the admin has disabled the ability for users to update their name format, requests to update this field for the primary login will silently fail. .PARAMETER IsDefault Whether this address is selected as the default "From:" address in situations such as composing a new message or sending a vacation auto-reply. Every Gmail account has exactly one default send-as address, so the only legal value that clients may write to this field is true. Changing this from false to true for an address will result in this field becoming false for the other previous default address. .PARAMETER ReplyToAddress An optional email address that is included in a "Reply-To:" header for mail sent using this alias. If this is empty, Gmail will not generate a "Reply-To:" header. .PARAMETER Signature An optional HTML signature that is included in messages composed with this alias in the Gmail web UI. .PARAMETER SmtpMsa An optional SMTP service that will be used as an outbound relay for mail sent using this alias. If this is empty, outbound mail will be sent directly from Gmail's servers to the destination SMTP service. This setting only applies to custom "from" aliases. Use the helper function Add-GmailSmtpMsa to create the correct object for this parameter. .PARAMETER TreatAsAlias Whether Gmail should treat this address as an alias for the user's primary email address. This setting only applies to custom "from" aliases. .EXAMPLE $smtpMsa = Add-GSGmailSmtpMsa -Host 10.0.30.18 -Port 3770 -SecurityMode none -Username mailadmin -Password $(ConvertTo-SecureString $password -AsPlainText -Force) New-GSGmailSendAsAlias -SendAsEmail joseph.wiggum@business.com -User joe@domain.com -Signature "<div>Thank you for your time,</br>Joseph Wiggum</div>" -SmtpMsa $smtpMsa Creates a new SendAs alias for Joe's formal/work address including signature and SmtpMsa settings. #> [OutputType('Google.Apis.Gmail.v1.Data.SendAs')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [string] $User, [parameter(Mandatory = $true)] [string] $SendAsEmail, [parameter(Mandatory = $false)] [string] $DisplayName, [parameter(Mandatory = $false)] [switch] $IsDefault, [parameter(Mandatory = $false)] [string] $ReplyToAddress, [parameter(Mandatory = $false)] [string] $Signature, [parameter(Mandatory = $false)] [Google.Apis.Gmail.v1.Data.SmtpMsa] $SmtpMsa, [parameter(Mandatory = $false)] [switch] $TreatAsAlias ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } if (-not $PSBoundParameters.ContainsKey('SendAsEmail')) { $SendAsEmail = $User } elseif ($SendAsEmail -notlike "*@*.*") { $SendAsEmail = "$($SendAsEmail)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = @( 'https://www.googleapis.com/auth/gmail.settings.sharing' ) ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams try { $body = New-Object 'Google.Apis.Gmail.v1.Data.SendAs' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) { $body.$prop = $PSBoundParameters[$prop] } $request = $service.Users.Settings.SendAs.Create($body,$User) Write-Verbose "Creating new SendAs alias '$SendAsEmail' for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'New-GSGmailSendAsAlias' function New-GSGmailSMIMEInfo { <# .SYNOPSIS Adds Gmail S/MIME info .DESCRIPTION Adds Gmail S/MIME info .PARAMETER SendAsEmail The email address that appears in the "From:" header for mail sent using this alias. .PARAMETER Pkcs12 PKCS#12 format containing a single private/public key pair and certificate chain. This format is only accepted from client for creating a new SmimeInfo and is never returned, because the private key is not intended to be exported. PKCS#12 may be encrypted, in which case encryptedKeyPassword should be set appropriately. .PARAMETER EncryptedKeyPassword Encrypted key password, when key is encrypted. .PARAMETER IsDefault Whether this SmimeInfo is the default one for this user's send-as address. .PARAMETER User The user's email address .EXAMPLE New-GSGmailSMIMEInfo -SendAsEmail 'joe@otherdomain.com' -Pkcs12 .\MyCert.pfx -User joe@domain.com Creates a specified S/MIME for Joe's SendAsEmail 'joe@otherdomain.com' using the provided PKCS12 certificate #> [OutputType('Google.Apis.Gmail.v1.Data.SmimeInfo')] [cmdletbinding()] Param ( [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $SendAsEmail, [parameter(Mandatory = $true)] [ValidateScript( { if (!(Test-Path $_)) { throw "Please enter a valid file path." } elseif ($_ -notlike "*.pfx" -and $_ -notlike "*.p12") { throw "Pkcs12 must be a .pfx or .p12 file" } else { $true } })] [string] $Pkcs12, [parameter(Mandatory = $false)] [SecureString] $EncryptedKeyPassword, [parameter(Mandatory = $false)] [Switch] $IsDefault, [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail", "UserKey", "Mail")] [ValidateNotNullOrEmpty()] [string] $User ) Process { Resolve-Email ([Ref]$User) $serviceParams = @{ Scope = @( 'https://www.googleapis.com/auth/gmail.settings.basic' 'https://www.googleapis.com/auth/gmail.settings.sharing' ) ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams try { $body = New-Object 'Google.Apis.Gmail.v1.Data.SmimeInfo' foreach ($key in $PSBoundParameters.Keys | Where-Object { $body.PSObject.Properties.Name -contains $_ }) { switch ($key) { EncryptedKeyPassword { $pw = (New-Object PSCredential "user", $PSBoundParameters[$key]).GetNetworkCredential().Password $body.EncryptedKeyPassword = $pw } Pkcs12 { $pkcs12Content = [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($Pkcs12)) $urlSafePkcs12Content = $pkcs12Content.Replace('+', '-').Replace('/', '_') $body.Pkcs12 = $urlSafePkcs12Content } Default { $body.$key = $PSBoundParameters[$key] } } } Write-Verbose "Adding new S/MIME of SendAsEmail '$SendAsEmail' for user '$User' using Certificate '$Pkcs12'" $request = $service.Users.Settings.SendAs.SmimeInfo.Insert($body, $User, $SendAsEmail) $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'New-GSGmailSMIMEInfo' function Remove-GSGmailFilter { <# .SYNOPSIS Removes a Gmail filter .DESCRIPTION Removes a Gmail filter .PARAMETER User The primary email of the user to remove the filter from Defaults to the AdminEmail user .PARAMETER FilterId The unique Id of the filter to remove .PARAMETER Raw If $true, returns the raw response. If not passed or -Raw:$false, response is formatted as a flat object for readability .EXAMPLE Remove-GSGmailFilter -FilterId ANe1Bmj5l3089jd3k1eQbY90g9rXswjS03LVOw Removes the Filter from the AdminEmail user after confirmation #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [Alias("Id")] [string[]] $FilterId, [parameter(Mandatory = $false)] [switch] $Raw ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/gmail.settings.basic' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams try { foreach ($fil in $FilterId) { if ($PSCmdlet.ShouldProcess("Deleting Filter Id '$fil' from user '$User'")) { Write-Verbose "Deleting Filter Id '$fil' from user '$User'" $request = $service.Users.Settings.Filters.Delete($User,$fil) $request.Execute() Write-Verbose "Filter Id '$fil' deleted successfully from user '$User'" } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Remove-GSGmailFilter' function Remove-GSGmailLabel { <# .SYNOPSIS Removes a Gmail label .DESCRIPTION Removes a Gmail label .PARAMETER LabelId The unique Id of the label to remove .PARAMETER User The primary email of the user to remove the label from Defaults to the AdminEmail user .EXAMPLE Remove-GSGmailLabel -LabelId ANe1Bmj5l3089jd3k1eQbY90g9rXswjS03LVOw Removes the Label from the AdminEmail user after confirmation #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $true, Position = 0, ValueFromPipelineByPropertyName = $true)] [Alias("Id")] [string[]] $LabelId, [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail", "UserKey", "Mail")] [ValidateNotNullOrEmpty()] [string] $User = $Script:PSGSuite.AdminEmail ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://mail.google.com' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams foreach ($label in $LabelId) { try { if ($PSCmdlet.ShouldProcess("Deleting Label Id '$label' from user '$User'")) { Write-Verbose "Deleting Label Id '$label' from user '$User'" $request = $service.Users.Labels.Delete($User, $label) $request.Execute() Write-Verbose "Label Id '$label' deleted successfully from user '$User'" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Remove-GSGmailLabel' function Remove-GSGmailMessage { <# .SYNOPSIS Removes a Gmail message from the user .DESCRIPTION Removes a Gmail message from the user .PARAMETER Id The Id of the message to remove .PARAMETER Filter The Gmail query to pull the list of messages to remove instead of passing the MessageId directly .PARAMETER MaxToModify The maximum amount of emails you would like to remove. Use this with the `Filter` parameter as a safeguard. .PARAMETER Method The method used to delete the message Available values are: * "Trash": moves the message to the TRASH label (Default - preferred method, as this is recoverable) * "Delete": permanently deletes the message (NON-RECOVERABLE!) Default value is 'Trash' .PARAMETER User The primary email of the user to remove the message from Defaults to the AdminEmail user .EXAMPLE Remove-GSGmailMessage -User joe -Id 161622d7b76b7e1e,1616227c34d435f2 Moves the 2 message Id's from Joe's inbox into their TRASH after confirmation #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High",DefaultParameterSetName = "MessageId")] Param ( [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = "MessageId")] [Alias('MessageId')] [String[]] $Id, [parameter(Mandatory = $true, ParameterSetName = "Filter")] [Alias('Query')] [string] $Filter, [parameter(Mandatory = $false,ParameterSetName = "Filter")] [int] $MaxToModify, [parameter(Mandatory = $false)] [ValidateSet('Trash','Delete')] [String] $Method = 'Trash', [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [string] $User = $Script:PSGSuite.AdminEmail ) Process { if ($MyInvocation.InvocationName -eq 'Move-GSGmailMessageToTrash') { $Method = 'Trash' } if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://mail.google.com' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $msgId = switch ($PSCmdlet.ParameterSetName) { MessageId { $Id } Filter { (Get-GSGmailMessageList -Filter $Filter -User $User).Id } } if ($PSBoundParameters.Keys -contains 'MaxToModify' -and $msgId.Count -gt $MaxToModify) { Write-Error "MaxToModify is set to $MaxToModify but total modifications are $($msgId.Count). No action taken." } else { $service = New-GoogleService @serviceParams try { foreach ($mId in $msgId) { $request = switch ($Method) { Trash { $service.Users.Messages.Trash($User,$mId) $message = "moved to TRASH" } Delete { $service.Users.Messages.Delete($User,$mId) $message = "deleted" } } if ($PSCmdlet.ShouldProcess("Removing Message Id '$mId' for user '$User'")) { Write-Verbose "Removing Message Id '$mId' for user '$User'" $res = $request.Execute() if ($res) { $res | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru } Write-Verbose "Message ID '$mId' successfully $message for user '$User'" } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Remove-GSGmailMessage' function Remove-GSGmailSendAsAlias { <# .SYNOPSIS Removes a Gmail SendAs alias. .DESCRIPTION Removes a Gmail SendAs alias. .PARAMETER SendAsEmail The SendAs alias to be removed. .PARAMETER User The email of the user you are removing the SendAs alias from. .EXAMPLE Remove-GSGmailSendAsAlias -SendAsEmail partyfuntime@domain.com -User joe@domain.com Remove Joe's fun custom Sendas alias that he had created in the early days of the company :-( #> [OutputType()] [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("SendAs")] [string[]] $SendAsEmail, [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [string] $User ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/gmail.settings.sharing' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams foreach ($sendAs in $SendAsEmail) { try { if ($sendAs -notlike "*@*.*") { $sendAs = "$($sendAs)@$($Script:PSGSuite.Domain)" } if ($PSCmdlet.ShouldProcess("Removing SendAs alias '$sendAs' from user '$User'")) { Write-Verbose "Removing SendAs alias '$sendAs' from user '$User'" $request = $service.Users.Settings.SendAs.Delete($User,$sendAs) $request.Execute() Write-Verbose "SendAs alias '$sendAs' successfully removed from user '$User'" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Remove-GSGmailSendAsAlias' function Remove-GSGmailSMIMEInfo { <# .SYNOPSIS Removes Gmail S/MIME info .DESCRIPTION Removes Gmail S/MIME info .PARAMETER SendAsEmail The email address that appears in the "From:" header for mail sent using this alias. .PARAMETER Id The immutable ID for the SmimeInfo .PARAMETER User The user's email address Defaults to the AdminEmail user .EXAMPLE Remove-GSGmailSMIMEInfo -SendAsEmail 'joe@otherdomain.com' -Id 1008396210820120578939 -User joe@domain.com Removes the specified S/MIME info for Joe's SendAsEmail 'joe@otherdomain.com'. #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [string] $SendAsEmail, [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [string[]] $Id, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [string] $User = $Script:PSGSuite.AdminEmail ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/gmail.settings.basic' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams foreach ($I in $Id) { try { if ($PSCmdlet.ShouldProcess("Removing S/MIME Id '$I' of SendAsEmail '$SendAsEmail' for user '$User'")) { $request = $service.Users.Settings.SendAs.SmimeInfo.Delete($User,$SendAsEmail,$I) $request.Execute() Write-Verbose "Successfully removed S/MIME Id '$I' of SendAsEmail '$SendAsEmail' for user '$User'" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Remove-GSGmailSMIMEInfo' function Restore-GSGmailMessage { <# .SYNOPSIS Restores a trashed message to the inbox .DESCRIPTION Restores a trashed message to the inbox .PARAMETER User The primary email of the user to restore the message for Defaults to the AdminEmail user .PARAMETER Id The Id of the message to restore .EXAMPLE Restore-GSGmailMessage -User joe -Id 161622d7b76b7e1e,1616227c34d435f2 Restores the 2 message Id's from Joe's TRASH back to their inbox #> [cmdletbinding()] Param ( [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [Alias('MessageID')] [String[]] $Id ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://mail.google.com' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams try { foreach ($mId in $Id) { $request = $service.Users.Messages.Untrash($User,$mId) Write-Verbose "Removing Message Id '$mId' from TRASH for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Restore-GSGmailMessage' function Send-GmailMessage { <# .SYNOPSIS Sends a Gmail message .DESCRIPTION Sends a Gmail message. Designed for parity with Send-GmailMessage .PARAMETER From The primary email of the user that is sending the message. This MUST be a user account owned by the customer, as the Gmail Service must be built under this user's context and will fail if a group or alias is passed instead Defaults to the AdminEmail user .PARAMETER Subject The subject of the email .PARAMETER Body The email body. Supports HTML when used in conjunction with the -BodyAsHtml parameter .PARAMETER To The To recipient(s) of the email .PARAMETER CC The Cc recipient(s) of the email .PARAMETER BCC The Bcc recipient(s) of the email .PARAMETER Attachments The attachment(s) of the email .PARAMETER BodyAsHtml If passed, renders the HTML content of the body on send .EXAMPLE Send-GmailMessage -From Joe -To john.doe@domain.com -Subject "New Pricing Models" -Body $body -BodyAsHtml -Attachments 'C:\Reports\PricingModel_2018.xlsx' Sends a message from Joe to john.doe@domain.com with HTML body and an Excel spreadsheet attached #> [cmdletbinding()] Param ( [parameter(Mandatory = $false)] [Alias("PrimaryEmail","UserKey","Mail","User")] [ValidateNotNullOrEmpty()] [String] $From = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $true)] [string] $Subject, [parameter(Mandatory = $false)] [string] $Body, [parameter(Mandatory = $false)] [string[]] $To, [parameter(Mandatory = $false)] [string[]] $CC, [parameter(Mandatory = $false)] [string[]] $BCC, [parameter(Mandatory = $false)] [ValidateScript( {Test-Path $_})] [string[]] $Attachments, [parameter(Mandatory = $false)] [switch] $BodyAsHtml ) Process { $User = $From -replace ".*<","" -replace ">","" if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail $From = $User } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" $From = $User } $serviceParams = @{ Scope = 'https://mail.google.com' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams $messageParams = @{ From = $From Subject = $Subject ReturnConstructedMessage = $true } if ($To) { $messageParams.Add("To",@($To)) } if ($Body) { $messageParams.Add("Body",$Body) } if ($CC) { $messageParams.Add("CC",@($CC)) } if ($BCC) { $messageParams.Add("BCC",@($BCC)) } if ($Attachments) { $messageParams.Add("Attachment",@($Attachments)) } if ($BodyAsHtml) { $messageParams.Add("BodyAsHtml",$true) } $raw = New-MimeMessage @messageParams | Convert-Base64 -From NormalString -To WebSafeBase64String try { $bodySend = New-Object 'Google.Apis.Gmail.v1.Data.Message' -Property @{ Raw = $raw } $request = $service.Users.Messages.Send($bodySend,$User) Write-Verbose "Sending Message '$Subject' from user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Send-GmailMessage' function Send-GSGmailSendAsConfirmation { <# .SYNOPSIS Sends a verification email to the specified send-as alias address. The verification status must be pending. .DESCRIPTION Sends a verification email to the specified send-as alias address. The verification status must be pending. .PARAMETER SendAsEmail The SendAs alias to be verified. .PARAMETER User The email of the user you are verifying the SendAs alias for. .EXAMPLE Send-GSGmailSendAsConfirmation -SendAsEmail joseph.wiggum@work.com -User joe@domain.com Sends a verification email to Joe's work address to confirm joe@domain.com as being able to send-as that account. #> [OutputType()] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("SendAs")] [string[]] $SendAsEmail, [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [string] $User ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/gmail.settings.sharing' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams foreach ($sendAs in $SendAsEmail) { try { if ($sendAs -notlike "*@*.*") { $sendAs = "$($sendAs)@$($Script:PSGSuite.Domain)" } Write-Verbose "Sending verification email to SendAs alias '$sendAs' for user '$User'" $request = $service.Users.Settings.SendAs.Verify($User,$sendAs) $request.Execute() Write-Verbose "Verification email for SendAs alias '$sendAs' of user '$User' sent successfully" } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Send-GSGmailSendAsConfirmation' function Update-GSGmailAutoForwardingSettings { <# .SYNOPSIS Updates the auto-forwarding setting for the specified account. A verified forwarding address must be specified when auto-forwarding is enabled. .DESCRIPTION Updates the auto-forwarding setting for the specified account. A verified forwarding address must be specified when auto-forwarding is enabled. .PARAMETER User The user to update the AutoForwarding settings for .PARAMETER Disposition The state that a message should be left in after it has been forwarded. Acceptable values are: * "archive": Archive the message. * "dispositionUnspecified": Unspecified disposition. * "leaveInInbox": Leave the message in the INBOX. * "markRead": Leave the message in the INBOX and mark it as read. * "trash": Move the message to the TRASH. .PARAMETER EmailAddress Email address to which all incoming messages are forwarded. This email address must be a verified member of the forwarding addresses. .PARAMETER Enabled Whether all incoming mail is automatically forwarded to another address. .EXAMPLE Update-GSGmailAutoForwardingSettings -User me -Disposition leaveInInbox -EmailAddress joe@domain.com -Enabled Enables auto forwarding of all mail for the AdminEmail user. Forwarded mail will be left in their inbox. #> [OutputType('Google.Apis.Gmail.v1.Data.AutoForwarding')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [string] $User, [parameter(Mandatory = $false)] [ValidateSet('archive','dispositionUnspecified','leaveInInbox','markRead','trash')] [string] $Disposition, [parameter(Mandatory = $false)] [string] $EmailAddress, [parameter(Mandatory = $false)] [switch] $Enabled ) Begin { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/gmail.settings.sharing' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams } Process { try { $body = New-Object 'Google.Apis.Gmail.v1.Data.AutoForwarding' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) { $body.$prop = $PSBoundParameters[$prop] } $request = $service.Users.Settings.UpdateAutoForwarding($body,$User) Write-Verbose "Updating AutoForwarding settings for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Update-GSGmailAutoForwardingSettings' function Update-GSGmailImapSettings { <# .SYNOPSIS Updates IMAP settings .DESCRIPTION Updates IMAP settings .PARAMETER User The user to update the IMAP settings for .PARAMETER AutoExpunge If this value is true, Gmail will immediately expunge a message when it is marked as deleted in IMAP. Otherwise, Gmail will wait for an update from the client before expunging messages marked as deleted. .PARAMETER Enabled Whether IMAP is enabled for the account. .PARAMETER ExpungeBehavior The action that will be executed on a message when it is marked as deleted and expunged from the last visible IMAP folder. Acceptable values are: * "archive": Archive messages marked as deleted. * "deleteForever": Immediately and permanently delete messages marked as deleted. The expunged messages cannot be recovered. * "expungeBehaviorUnspecified": Unspecified behavior. * "trash": Move messages marked as deleted to the trash. .PARAMETER MaxFolderSize An optional limit on the number of messages that an IMAP folder may contain. Legal values are 0, 1000, 2000, 5000 or 10000. A value of zero is interpreted to mean that there is no limit. .EXAMPLE Update-GSGmailImapSettings -Enabled:$false -User me Disables IMAP for the AdminEmail user #> [OutputType('Google.Apis.Gmail.v1.Data.ImapSettings')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [string] $User, [parameter(Mandatory = $false)] [switch] $AutoExpunge, [parameter(Mandatory = $false)] [switch] $Enabled, [parameter(Mandatory = $false)] [ValidateSet('archive','deleteForever','expungeBehaviorUnspecified','trash')] [string] $ExpungeBehavior, [parameter(Mandatory = $false)] [ValidateSet(0,1000,2000,5000,10000)] [int] $MaxFolderSize ) Begin { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/gmail.settings.basic' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams } Process { try { $body = New-Object 'Google.Apis.Gmail.v1.Data.ImapSettings' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) { $body.$prop = $PSBoundParameters[$prop] } $request = $service.Users.Settings.UpdateImap($body,$User) Write-Verbose "Updating IMAP settings for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Update-GSGmailImapSettings' function Update-GSGmailLabel { <# .SYNOPSIS Updates Gmail label information for the specified labelid .DESCRIPTION Updates Gmail label information for the specified labelid .PARAMETER LabelId The unique Id of the label to update .PARAMETER LabelListVisibility The visibility of the label in the label list in the Gmail web interface. Acceptable values are: * "labelHide": Do not show the label in the label list. * "labelShow": Show the label in the label list. (Default) * "labelShowIfUnread": Show the label if there are any unread messages with that label. .PARAMETER MessageListVisibility The visibility of messages with this label in the message list in the Gmail web interface. Acceptable values are: * "hide": Do not show the label in the message list. * "show": Show the label in the message list. (Default) .PARAMETER Name The display name of the label .PARAMETER BackgroundColor The background color of the label Options and their corresponding hex code: Amethyst = '#8e63ce' BananaMania = '#fce8b3' Bermuda = '#68dfa9' BilobaFlower = '#b694e8' Black = '#000000' BlueRomance = '#c6f3de' BrandyPunch = '#cf8933' BurntSienna = '#e66550' Cadillac = '#b65775' Camelot = '#83334c' CeruleanBlue = '#285bac' ChathamsBlue = '#1c4587' Concrete = '#f3f3f3' CornflowerBlue = '#4a86e8' LightCornflowerBlue = '#6d9eeb' CreamCan = '#f2c960' Cupid = '#fbc8d9' DeepBlush = '#e07798' Desert = '#a46a21' DoveGray = '#666666' DustyGray = '#999999' Eucalyptus = '#2a9c68' Flesh = '#ffd6a2' FringyFlower = '#b9e4d0' Gallery = '#efefef' Goldenrod = '#fad165' Illusion = '#f7a7c0' Jewel = '#1a764d' Koromiko = '#ffbc6b' LightMountainMeadow = '#16a766' LightShamrock = '#43d692' LuxorGold = '#aa8831' MandysPink = '#f6c5be' MediumPurple = '#a479e2' Meteorite = '#41236d' MoonRaker = '#d0bcf1' LightMoonRaker = '#e4d7f5' MountainMeadow = '#149e60' Oasis = '#fef1d1' OceanGreen = '#44b984' OldGold = '#d5ae49' Perano = '#a4c2f4' PersianPink = '#f691b3' PigPink = '#fcdee8' Pueblo = '#822111' RedOrange = '#fb4c2f' RoyalBlue = '#3c78d8' RoyalPurple = '#653e9b' Salem = '#0b804b' Salomie = '#fcda83' SeaPink = '#efa093' Shamrock = '#3dc789' Silver = '#cccccc' Tabasco = '#ac2b16' Tequila = '#ffe6c7' Thunderbird = '#cc3a21' TropicalBlue = '#c9daf8' TulipTree = '#eaa041' Tundora = '#434343' VistaBlue = '#89d3b2' Watercourse = '#076239' WaterLeaf = '#a0eac9' White = '#ffffff' YellowOrange = '#ffad47' .PARAMETER TextColor The text color of the label Options and their corresponding hex code: Amethyst = '#8e63ce' BananaMania = '#fce8b3' Bermuda = '#68dfa9' BilobaFlower = '#b694e8' Black = '#000000' BlueRomance = '#c6f3de' BrandyPunch = '#cf8933' BurntSienna = '#e66550' Cadillac = '#b65775' Camelot = '#83334c' CeruleanBlue = '#285bac' ChathamsBlue = '#1c4587' Concrete = '#f3f3f3' CornflowerBlue = '#4a86e8' LightCornflowerBlue = '#6d9eeb' CreamCan = '#f2c960' Cupid = '#fbc8d9' DeepBlush = '#e07798' Desert = '#a46a21' DoveGray = '#666666' DustyGray = '#999999' Eucalyptus = '#2a9c68' Flesh = '#ffd6a2' FringyFlower = '#b9e4d0' Gallery = '#efefef' Goldenrod = '#fad165' Illusion = '#f7a7c0' Jewel = '#1a764d' Koromiko = '#ffbc6b' LightMountainMeadow = '#16a766' LightShamrock = '#43d692' LuxorGold = '#aa8831' MandysPink = '#f6c5be' MediumPurple = '#a479e2' Meteorite = '#41236d' MoonRaker = '#d0bcf1' LightMoonRaker = '#e4d7f5' MountainMeadow = '#149e60' Oasis = '#fef1d1' OceanGreen = '#44b984' OldGold = '#d5ae49' Perano = '#a4c2f4' PersianPink = '#f691b3' PigPink = '#fcdee8' Pueblo = '#822111' RedOrange = '#fb4c2f' RoyalBlue = '#3c78d8' RoyalPurple = '#653e9b' Salem = '#0b804b' Salomie = '#fcda83' SeaPink = '#efa093' Shamrock = '#3dc789' Silver = '#cccccc' Tabasco = '#ac2b16' Tequila = '#ffe6c7' Thunderbird = '#cc3a21' TropicalBlue = '#c9daf8' TulipTree = '#eaa041' Tundora = '#434343' VistaBlue = '#89d3b2' Watercourse = '#076239' WaterLeaf = '#a0eac9' White = '#ffffff' YellowOrange = '#ffad47' .PARAMETER User The user to update label information for Defaults to the AdminEmail user .EXAMPLE Update-GSGmailLabel -User user@domain.com -LabelId Label_79 -BackgroundColor Black -TextColor Bermuda Updates the specified Gmail label with new background and text colors .EXAMPLE Get-GSGmailLabel | Where-Object {$_.LabelListVisibility -eq 'labelShowIfUnread'} | Update-GSGmailLabel -LabelListVisibility labelShow -BackgroundColor Bermuda -TextColor Tundora Updates all labels with LabelListVisibility of 'labelShowIfUnread' with new background and text colors and sets all of them to always show #> [OutputType('Google.Apis.Gmail.v1.Data.Label')] [cmdletbinding()] Param ( [parameter(Mandatory = $true, Position = 0, ValueFromPipelineByPropertyName = $true)] [Alias("Id")] [string[]] $LabelId, [parameter(Mandatory = $false, Position = 1)] [ValidateSet("labelHide","labelShow","labelShowIfUnread")] [string] $LabelListVisibility, [parameter(Mandatory = $false, Position = 2)] [ValidateSet("hide","show")] [string] $MessageListVisibility, [parameter(Mandatory = $false, Position = 3)] [string] $Name, [parameter(Mandatory = $false)] [ValidateSet('Amethyst','BananaMania','Bermuda','BilobaFlower','Black','BlueRomance','BrandyPunch','BurntSienna','Cadillac','Camelot','CeruleanBlue','ChathamsBlue','Concrete','CornflowerBlue','CreamCan','Cupid','DeepBlush','Desert','DoveGray','DustyGray','Eucalyptus','Flesh','FringyFlower','Gallery','Goldenrod','Illusion','Jewel','Koromiko','LightCornflowerBlue','LightMoonRaker','LightMountainMeadow','LightShamrock','LuxorGold','MandysPink','MediumPurple','Meteorite','MoonRaker','MountainMeadow','Oasis','OceanGreen','OldGold','Perano','PersianPink','PigPink','Pueblo','RedOrange','RoyalBlue','RoyalPurple','Salem','Salomie','SeaPink','Shamrock','Silver','Tabasco','Tequila','Thunderbird','TropicalBlue','TulipTree','Tundora','VistaBlue','Watercourse','WaterLeaf','White','YellowOrange')] [string] $BackgroundColor, [parameter(Mandatory = $false)] [ValidateSet('Amethyst','BananaMania','Bermuda','BilobaFlower','Black','BlueRomance','BrandyPunch','BurntSienna','Cadillac','Camelot','CeruleanBlue','ChathamsBlue','Concrete','CornflowerBlue','CreamCan','Cupid','DeepBlush','Desert','DoveGray','DustyGray','Eucalyptus','Flesh','FringyFlower','Gallery','Goldenrod','Illusion','Jewel','Koromiko','LightCornflowerBlue','LightMoonRaker','LightMountainMeadow','LightShamrock','LuxorGold','MandysPink','MediumPurple','Meteorite','MoonRaker','MountainMeadow','Oasis','OceanGreen','OldGold','Perano','PersianPink','PigPink','Pueblo','RedOrange','RoyalBlue','RoyalPurple','Salem','Salomie','SeaPink','Shamrock','Silver','Tabasco','Tequila','Thunderbird','TropicalBlue','TulipTree','Tundora','VistaBlue','Watercourse','WaterLeaf','White','YellowOrange')] [string] $TextColor, [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail", "UserKey", "Mail")] [ValidateNotNullOrEmpty()] [string] $User = $Script:PSGSuite.AdminEmail ) Begin { $colorDict = @{ Amethyst = '#8e63ce' BananaMania = '#fce8b3' Bermuda = '#68dfa9' BilobaFlower = '#b694e8' Black = '#000000' BlueRomance = '#c6f3de' BrandyPunch = '#cf8933' BurntSienna = '#e66550' Cadillac = '#b65775' Camelot = '#83334c' CeruleanBlue = '#285bac' ChathamsBlue = '#1c4587' Concrete = '#f3f3f3' CornflowerBlue = '#4a86e8' LightCornflowerBlue = '#6d9eeb' CreamCan = '#f2c960' Cupid = '#fbc8d9' DeepBlush = '#e07798' Desert = '#a46a21' DoveGray = '#666666' DustyGray = '#999999' Eucalyptus = '#2a9c68' Flesh = '#ffd6a2' FringyFlower = '#b9e4d0' Gallery = '#efefef' Goldenrod = '#fad165' Illusion = '#f7a7c0' Jewel = '#1a764d' Koromiko = '#ffbc6b' LightMountainMeadow = '#16a766' LightShamrock = '#43d692' LuxorGold = '#aa8831' MandysPink = '#f6c5be' MediumPurple = '#a479e2' Meteorite = '#41236d' MoonRaker = '#d0bcf1' LightMoonRaker = '#e4d7f5' MountainMeadow = '#149e60' Oasis = '#fef1d1' OceanGreen = '#44b984' OldGold = '#d5ae49' Perano = '#a4c2f4' PersianPink = '#f691b3' PigPink = '#fcdee8' Pueblo = '#822111' RedOrange = '#fb4c2f' RoyalBlue = '#3c78d8' RoyalPurple = '#653e9b' Salem = '#0b804b' Salomie = '#fcda83' SeaPink = '#efa093' Shamrock = '#3dc789' Silver = '#cccccc' Tabasco = '#ac2b16' Tequila = '#ffe6c7' Thunderbird = '#cc3a21' TropicalBlue = '#c9daf8' TulipTree = '#eaa041' Tundora = '#434343' VistaBlue = '#89d3b2' Watercourse = '#076239' WaterLeaf = '#a0eac9' White = '#ffffff' YellowOrange = '#ffad47' } } Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://mail.google.com' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams $body = New-Object 'Google.Apis.Gmail.v1.Data.Label' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) { $body.$prop = $PSBoundParameters[$prop] } if ($PSBoundParameters.Keys -contains 'BackgroundColor' -or $PSBoundParameters.Keys -contains 'TextColor') { $color = New-Object 'Google.Apis.Gmail.v1.Data.LabelColor' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$color.PSObject.Properties.Name -contains $_}) { $color.$prop = $colorDict[$PSBoundParameters[$prop]] } $body.Color = $color } foreach ($label in $LabelId) { try { Write-Verbose "Updating Label Id '$label' for user '$User'" $request = $service.Users.Labels.Patch($body, $User, $label) $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Update-GSGmailLabel' function Update-GSGmailLanguageSettings { <# .SYNOPSIS Updates Gmail display language settings .DESCRIPTION Updates Gmail display language settings .PARAMETER User The user to update the Gmail display language settings for .PARAMETER Language The language to display Gmail in, formatted as an RFC 3066 Language Tag (for example en-GB, fr or ja for British English, French, or Japanese respectively). The set of languages supported by Gmail evolves over time, so please refer to the "Language" dropdown in the Gmail settings for all available options, as described in the language settings help article. A table of sample values is also provided in the Managing Language Settings guide Not all Gmail clients can display the same set of languages. In the case that a user's display language is not available for use on a particular client, said client automatically chooses to display in the closest supported variant (or a reasonable default). .EXAMPLE Update-GSGmailLanguageSettings -User me -Language fr Updates the Gmail display language to French for the AdminEmail user. #> [OutputType('Google.Apis.Gmail.v1.Data.LanguageSettings')] [cmdletbinding()] Param ( [parameter(Mandatory,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [string[]] $User, [parameter(Mandatory,Position = 1)] [ValidateSet('af','am','ar','az','bg','bn','ca','chr','cs','cy','da','de','el','en','en-GB','es','es-419','et','eu','fa','fi','fil','fr','fr-CA','ga','gl','gu','he','hi','hr','hu','hy','id','is','it','ja','ka','km','kn','ko','lo','lt','lv','ml','mn','mr','ms','my','ne','nl','no','pl','pt-BR','pt-PT','ro','ru','si','sk','sl','sr','sv','sw','ta','te','th','tr','uk','ur','vi','zh-CN','zh-HK','zh-TW','zu')] [string] $Language ) Process { foreach ($U in $User) { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/gmail.settings.basic' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $U } $service = New-GoogleService @serviceParams try { $body = New-Object 'Google.Apis.Gmail.v1.Data.LanguageSettings' -Property @{ DisplayLanguage = $Language } $request = $service.Users.Settings.UpdateLanguage($body,$U) Write-Verbose "Updating Gmail Display Language for user '$U' to '$Language'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Update-GSGmailLanguageSettings' function Update-GSGmailMessageLabels { <# .SYNOPSIS Updates Gmail label information for the specified message .DESCRIPTION Updates Gmail label information for the specified message .PARAMETER MessageId The unique Id of the message to update. .PARAMETER Filter The Gmail query to pull the list of messages to update instead of passing the MessageId directly. .PARAMETER MaxToModify The maximum amount of emails you would like to remove. Use this with the `Filter` parameter as a safeguard. .PARAMETER AddLabel The label(s) to add to the message. This supports either the unique LabelId or the Display Name for the label .PARAMETER RemoveLabel The label(s) to remove from the message. This supports either the unique LabelId or the Display Name for the label .PARAMETER User The user to update message labels for Defaults to the AdminEmail user .EXAMPLE Set-GSGmailLabel -user user@domain.com -LabelId Label_798170282134616520 - Gets the Gmail labels of the AdminEmail user #> [cmdletbinding(DefaultParameterSetName = "MessageId")] Param ( [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "MessageId")] [Alias("Id")] [string[]] $MessageId, [parameter(Mandatory = $true, ParameterSetName = "Filter")] [Alias('Query')] [string] $Filter, [parameter(Mandatory = $false,ParameterSetName = "Filter")] [int] $MaxToModify, [parameter(Mandatory = $false)] [string[]] $AddLabel, [parameter(Mandatory = $false)] [string[]] $RemoveLabel, [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail", "UserKey", "Mail")] [ValidateNotNullOrEmpty()] [string] $User = $Script:PSGSuite.AdminEmail ) Process { if ($PSBoundParameters.Keys -notcontains 'AddLabel' -and $PSBoundParameters.Keys -notcontains 'RemoveLabel') { throw "You must specify a value for either AddLabel or RemoveLabel!" } if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://mail.google.com' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $msgId = switch ($PSCmdlet.ParameterSetName) { MessageId { $MessageId } Filter { (Get-GSGmailMessageList -Filter $Filter -User $User).Id } } if ($PSBoundParameters.Keys -contains 'MaxToModify' -and $msgId.Count -gt $MaxToModify) { Write-Error "MaxToModify is set to $MaxToModify but total modifications are $($msgId.Count). No action taken." } else { $service = New-GoogleService @serviceParams $userLabels = @{} Get-GSGmailLabel -User $User -Verbose:$false | ForEach-Object { $userLabels[$_.Name] = $_.Id } $body = New-Object 'Google.Apis.Gmail.v1.Data.ModifyMessageRequest' if ($PSBoundParameters.Keys -contains 'AddLabel') { $addLs = New-Object 'System.Collections.Generic.List[System.String]' foreach ($label in $AddLabel) { try { $addLs.Add($userLabels[$label]) } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } $body.AddLabelIds = $addLs } if ($PSBoundParameters.Keys -contains 'RemoveLabel') { $remLs = New-Object 'System.Collections.Generic.List[System.String]' foreach ($label in $RemoveLabel) { try { $remLs.Add($userLabels[$label]) } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } $body.RemoveLabelIds = $remLs } foreach ($message in $msgId) { try { $request = $service.Users.Messages.Modify($body, $User, $message) Write-Verbose "Updating Labels on Message '$message' for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } } Export-ModuleMember -Function 'Update-GSGmailMessageLabels' function Update-GSGmailPopSettings { <# .SYNOPSIS Updates POP settings .DESCRIPTION Updates POP settings .PARAMETER User The user to update the POP settings for .PARAMETER AccessWindow The range of messages which are accessible via POP. Acceptable values are: * "accessWindowUnspecified": Unspecified range. * "allMail": Indicates that all unfetched messages are accessible via POP. * "disabled": Indicates that no messages are accessible via POP. * "fromNowOn": Indicates that unfetched messages received after some past point in time are accessible via POP. .PARAMETER Disposition The action that will be executed on a message after it has been fetched via POP. Acceptable values are: * "archive": Archive the message. * "dispositionUnspecified": Unspecified disposition. * "leaveInInbox": Leave the message in the INBOX. * "markRead": Leave the message in the INBOX and mark it as read. * "trash": Move the message to the TRASH. .EXAMPLE Update-GSGmailPopSettings -User me -AccessWindow allMail Sets the POP AccessWindow to 'allMail' for the AdminEmail user #> [OutputType('Google.Apis.Gmail.v1.Data.PopSettings')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [string] $User, [parameter(Mandatory = $false)] [ValidateSet('accessWindowUnspecified','allMail','disabled','fromNowOn')] [string] $AccessWindow, [parameter(Mandatory = $false)] [ValidateSet('archive','dispositionUnspecified','leaveInInbox','markRead','trash')] [string] $Disposition ) Begin { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/gmail.settings.basic' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams } Process { try { $body = New-Object 'Google.Apis.Gmail.v1.Data.PopSettings' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) { $body.$prop = $PSBoundParameters[$prop] } $request = $service.Users.Settings.UpdatePop($body,$User) Write-Verbose "Updating POP settings for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Update-GSGmailPopSettings' function Update-GSGmailSendAsAlias { <# .SYNOPSIS Updates SendAs alias settings for a user. .DESCRIPTION Updates SendAs alias settings for a user. .PARAMETER User The user to update the SendAs settings for. .PARAMETER SendAsEmail The SendAs alias to be updated. .PARAMETER DisplayName A name that appears in the "From:" header for mail sent using this alias. For custom "from" addresses, when this is empty, Gmail will populate the "From:" header with the name that is used for the primary address associated with the account. If the admin has disabled the ability for users to update their name format, requests to update this field for the primary login will silently fail. .PARAMETER IsDefault Whether this address is selected as the default "From:" address in situations such as composing a new message or sending a vacation auto-reply. Every Gmail account has exactly one default send-as address, so the only legal value that clients may write to this field is true. Changing this from false to true for an address will result in this field becoming false for the other previous default address. .PARAMETER ReplyToAddress An optional email address that is included in a "Reply-To:" header for mail sent using this alias. If this is empty, Gmail will not generate a "Reply-To:" header. .PARAMETER Signature An optional HTML signature that is included in messages composed with this alias in the Gmail web UI. .PARAMETER SmtpMsa An optional SMTP service that will be used as an outbound relay for mail sent using this alias. If this is empty, outbound mail will be sent directly from Gmail's servers to the destination SMTP service. This setting only applies to custom "from" aliases. Use the helper function Add-GmailSmtpMsa to create the correct object for this parameter. .PARAMETER TreatAsAlias Whether Gmail should treat this address as an alias for the user's primary email address. This setting only applies to custom "from" aliases. .EXAMPLE $smtpMsa = Add-GSGmailSmtpMsa -Host 10.0.30.18 -Port 3770 -SecurityMode none -Username mailadmin -Password $(ConvertTo-SecureString $password -AsPlainText -Force) Update-GSGmailSendAsAlias -SendAsEmail joseph.wiggum@business.com -User joe@domain.com -Signature "<div>Thank you for your time,</br>Joseph Wiggum</div>" -SmtpMsa $smtpMsa Updates Joe's SendAs settings for his formal alias, including signature and SmtpMsa settings. #> [OutputType('Google.Apis.Gmail.v1.Data.SendAs')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [string] $User, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [string] $SendAsEmail, [parameter(Mandatory = $false)] [string] $DisplayName, [parameter(Mandatory = $false)] [switch] $IsDefault, [parameter(Mandatory = $false)] [string] $ReplyToAddress, [parameter(Mandatory = $false)] [string] $Signature, [parameter(Mandatory = $false)] [Google.Apis.Gmail.v1.Data.SmtpMsa] $SmtpMsa, [parameter(Mandatory = $false)] [switch] $TreatAsAlias ) Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } if (-not $PSBoundParameters.ContainsKey('SendAsEmail')) { $SendAsEmail = $User } elseif ($SendAsEmail -notlike "*@*.*") { $SendAsEmail = "$($SendAsEmail)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = @( 'https://www.googleapis.com/auth/gmail.settings.sharing', 'https://www.googleapis.com/auth/gmail.settings.basic' ) ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams try { $body = New-Object 'Google.Apis.Gmail.v1.Data.SendAs' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) { $body.$prop = $PSBoundParameters[$prop] } $request = $service.Users.Settings.SendAs.Patch($body,$User,$SendAsEmail) Write-Verbose "Updating SendAs settings of alias '$SendAsEmail' for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Update-GSGmailSendAsAlias' function Update-GSGmailSignature { <# .SYNOPSIS Updates the Gmail signature for a user's SendAs alias. .DESCRIPTION Updates the Gmail signature for a user's SendAs alias. .PARAMETER User The user to update the SendAs signature for. .PARAMETER SendAsEmail The SendAs alias to be updated. Defaults to the User specified if this parameter is excluded. .PARAMETER Signature An HTML signature that is included in messages composed with this alias in the Gmail web UI. .PARAMETER SignatureFile A file containing the HTML signature that is included in messages composed with this alias in the Gmail web UI. The file will be read in with Get-Content $SignatureFile -Raw. .PARAMETER AsPlainText If $true, this wraps your Signature or SignatureFile contents in standard HTML that Google would normally add (div wrappers around each line with font-size:small on all and empty lines replaced with <br>). .EXAMPLE Update-GSGmailSignature -SendAsEmail joseph.wiggum@business.com -User joe@domain.com -Signature "<div>Thank you for your time,</br>Joseph Wiggum</div>" Updates Joe's SendAs signature for his formal alias. #> [OutputType('Google.Apis.Gmail.v1.Data.SendAs')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [string] $User, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [string] $SendAsEmail, [parameter(Mandatory = $true,ValueFromPipeline = $true,ParameterSetName = "Signature")] [string] $Signature, [parameter(Mandatory = $true,ParameterSetName = "SignatureFile")] [ValidateScript({ try { Test-Path $_ -ErrorAction Stop $true } catch { throw $_ } })] [string] $SignatureFile, [parameter(Mandatory = $false)] [switch] $AsPlainText ) Begin { $finalSignature = if ($AsPlainText) { $baseSignature = if ($PSCmdlet.ParameterSetName -eq 'SignatureFile') { Get-Content $SignatureFile } else { $Signature -split "[\r\n]" | Where-Object {$_} } $fmtSignature = @('<div dir="ltr"><br><div>') foreach ($line in $baseSignature) { if ([String]::IsNullOrEmpty(($line -replace "\s"))) { $fmtSignature += '<div style="font-size:small"><br></div>' } else { $fmtSignature += "<div style=`"font-size:small`">$line</div>" } } $fmtSignature += '</div></div>' $fmtSignature -join "" } elseif ($PSCmdlet.ParameterSetName -eq 'SignatureFile') { Get-Content $SignatureFile -Raw } else { $Signature } } Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } if (-not $PSBoundParameters.ContainsKey('SendAsEmail')) { $SendAsEmail = $User } elseif ($SendAsEmail -notlike "*@*.*") { $SendAsEmail = "$($SendAsEmail)@$($Script:PSGSuite.Domain)" } try { Write-Verbose "Setting the following signature on SendAs alias '$SendAsEmail' for user '$User':`n`n$finalSignature`n" Update-GSGmailSendAsAlias -SendAsEmail $SendAsEmail -User $User -Signature $finalSignature } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Update-GSGmailSignature' function Update-GSGmailVacationSettings { <# .SYNOPSIS Updates vacation responder settings for the specified account. .DESCRIPTION Updates vacation responder settings for the specified account. .PARAMETER User The user to update the VacationSettings settings for .PARAMETER EnableAutoReply Flag that controls whether Gmail automatically replies to messages. .PARAMETER EndTime An optional end time for sending auto-replies. When this is specified, Gmail will automatically reply only to messages that it receives before the end time. If both startTime and endTime are specified, startTime must precede endTime. .PARAMETER ResponseBodyHtml Response body in HTML format. Gmail will sanitize the HTML before storing it. .PARAMETER ResponseBodyPlainText Response body in plain text format. .PARAMETER ResponseSubject Optional text to prepend to the subject line in vacation responses. In order to enable auto-replies, either the response subject or the response body must be nonempty. .PARAMETER RestrictToContacts Flag that determines whether responses are sent to recipients who are not in the user's list of contacts. .PARAMETER RestrictToDomain Flag that determines whether responses are sent to recipients who are outside of the user's domain. This feature is only available for G Suite users. .PARAMETER StartTime An optional start time for sending auto-replies. When this is specified, Gmail will automatically reply only to messages that it receives after the start time. If both startTime and endTime are specified, startTime must precede endTime. .EXAMPLE Update-GSGmailVacationSettings -User me -ResponseBodyHtml "I'm on vacation and will reply when I'm back in the office. Thanks!" -RestrictToDomain -EndTime (Get-Date).AddDays(7) -StartTime (Get-Date) -EnableAutoReply Enables the vacation auto-reply for the AdminEmail user. Auto-replies will be sent to other users in the same domain only. The vacation response is enabled for 7 days from the time that the command is sent. .EXAMPLE Update-GSGmailVacationSettings -User me -EnableAutoReply:$false Disables the vacaction auto-response for the AdminEmail user immediately. #> [OutputType('Google.Apis.Gmail.v1.Data.VacationSettings')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [string] $User, [parameter(Mandatory = $false)] [switch] $EnableAutoReply, [parameter(Mandatory = $false)] [datetime] $EndTime, [parameter(Mandatory = $false)] [string] $ResponseBodyHtml, [parameter(Mandatory = $false)] [string] $ResponseBodyPlainText, [parameter(Mandatory = $false)] [string] $ResponseSubject, [parameter(Mandatory = $false)] [switch] $RestrictToContacts, [parameter(Mandatory = $false)] [switch] $RestrictToDomain, [parameter(Mandatory = $false)] [datetime] $StartTime ) Begin { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/gmail.settings.basic' ServiceType = 'Google.Apis.Gmail.v1.GmailService' User = $User } $service = New-GoogleService @serviceParams } Process { try { $body = New-Object 'Google.Apis.Gmail.v1.Data.VacationSettings' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) { switch ($prop) { StartTime { $epochMs = (Convert-DateToEpoch $StartTime) $body.$prop = [long]$epochMs } EndTime { $epochMs = (Convert-DateToEpoch $EndTime) $body.$prop = [long]$epochMs } Default { $body.$prop = $PSBoundParameters[$prop] } } } $request = $service.Users.Settings.UpdateVacation($body,$User) Write-Verbose "Updating Vacation settings for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Update-GSGmailVacationSettings' function Add-GSGroupMember { <# .SYNOPSIS Adds a list of emails to a target group .DESCRIPTION Adds a list of emails to a target group. Designed for parity with Add-ADGroupMember .PARAMETER Identity The email or GroupID of the target group to add members to .PARAMETER Member The list of user and/or group emails that you would like to add to the target group .PARAMETER Role The role that you would like to add the members as Defaults to "MEMBER" .EXAMPLE Add-GSGroupMember "admins@domain.com" -Member "joe-admin@domain.com","sally.admin@domain.com" Adds 2 users to the group "admins@domain.com" #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Member')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias('GroupEmail','Group','Email')] [String] $Identity, [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true,Position = 1)] [Alias("PrimaryEmail","UserKey","Mail","User","UserEmail","Members")] [ValidateNotNullOrEmpty()] [String[]] $Member, [parameter(Mandatory = $false)] [ValidateSet("MEMBER","MANAGER","OWNER")] [String] $Role = "MEMBER" ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.group' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { Resolve-Email ([ref]$Identity) -IsGroup $groupObj = Get-GSGroup -Group $Identity -Verbose:$false foreach ($U in $Member) { try { 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 $body.Role = $Role $request = $service.Members.Insert($body,$groupObj.Id) $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Group' -Value $Identity -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Add-GSGroupMember' function Add-GSPrincipalGroupMembership { <# .SYNOPSIS Adds the target email to a list of groups .DESCRIPTION Adds the target email to a list of groups. Designed for parity with Add-ADPrincipalGroupMembership .PARAMETER Identity The user or group email that you would like to add to the list of groups .PARAMETER MemberOf The list of groups to add the target email to .PARAMETER Role The role that you would like to add the members as Defaults to "MEMBER" .EXAMPLE Add-GSPrincipalGroupMembership "joe@domain.com" -MemberOf "admins@domain.com","users@domain.com" Adds the email "joe@domain.com" to the admins@ and users@ groups #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Member')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail","User","Email","UserEmail")] [String] $Identity, [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true,Position = 1)] [Alias('GroupEmail','Group')] [ValidateNotNullOrEmpty()] [String[]] $MemberOf, [parameter(Mandatory = $false)] [ValidateSet("MEMBER","MANAGER","OWNER")] [String] $Role = "MEMBER" ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.group' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { Resolve-Email ([ref]$Identity) try { foreach ($U in $MemberOf) { $groupObj = Get-GSGroup -Group $U -Verbose:$false 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 $groupObj.Email -PassThru } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Add-GSPrincipalGroupMembership' function Get-GSGroup { <# .SYNOPSIS Gets the specified group's information. Returns the full group list if -Group is excluded .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 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 .PARAMETER Where_IsAMember Include a user email here to get the list of groups that user is a member of .PARAMETER Domain The domain name. Use this field to get fields from only one domain. To return groups for all domains you own, exclude this parameter .PARAMETER Fields The fields to return in the response .PARAMETER PageSize Page size of the result set Defaults to 200 .PARAMETER Limit The maximum amount of results you want returned. Exclude or set to 0 to return all results .EXAMPLE Get-GSGroup -Where_IsAMember "joe@domain.com" Gets the list of groups that joe@domain.com is a member of .EXAMPLE Get-GSGroup -Domain mysubdomain.org Gets the list of groups only for the 'mysubdomain.org' domain. .EXAMPLE Get-GSGroup -Filter "email:support*" Gets all the groups with emails beginning with 'support' .EXAMPLE Get-GSGroup -Filter "name -eq 'IT HelpDesk'" Gets the IT HelpDesk group by name using PowerShell syntax. PowerShell syntax is supported as a best effort, please refer to the Group Search documentation from Google for exact syntax. #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Group')] [cmdletbinding(DefaultParameterSetName = "ListFilter")] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = "Get")] [Alias("Email","Group","GroupEmail")] [ValidateNotNullOrEmpty()] [String[]] $Identity, [parameter(Mandatory = $false,ParameterSetName = "ListFilter")] [Alias('Query')] [string] $Filter, [parameter(Mandatory = $false,ParameterSetName = "ListWhereMember")] [Alias('UserKey')] [String] $Where_IsAMember, [parameter(Mandatory = $false,ParameterSetName = "ListFilter")] [string] $Domain, [parameter(Mandatory = $false,ParameterSetName = "Get")] [String[]] $Fields, [parameter(Mandatory = $false,ParameterSetName = "ListFilter")] [parameter(Mandatory = $false,ParameterSetName = "ListWhereMember")] [ValidateRange(1,200)] [Alias("MaxResults")] [Int] $PageSize = 200, [parameter(Mandatory = $false,ParameterSetName = "ListFilter")] [parameter(Mandatory = $false,ParameterSetName = "ListWhereMember")] [Alias('First')] [Int] $Limit = 0 ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.group' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { switch -Regex ($PSCmdlet.ParameterSetName) { Get { foreach ($G in $Identity) { try { Resolve-Email ([ref]$G) -IsGroup Write-Verbose "Getting group '$G'" $request = $service.Groups.Get($G) if ($Fields) { $request.Fields = "$($Fields -join ",")" } $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Group' -Value $G -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } 'List.*' { $verbString = "Getting all G Suite Groups" try { $request = $service.Groups.List() if ($PSBoundParameters.Keys -contains 'Where_IsAMember') { Resolve-Email ([ref]$Where_IsAMember) $verbString += " where '$Where_IsAMember' is a member" $request.UserKey = $Where_IsAMember } elseif ($PSBoundParameters.Keys -contains 'Filter') { if ($Filter -eq '*') { $Filter = "" } else { $Filter = "$($Filter -join " ")" } $Filter = $Filter -replace " -eq ","=" -replace " -like ",":" -replace " -match ",":" -replace " -contains ",":" -creplace "'True'","True" -creplace "'False'","False" if (-not [String]::IsNullOrEmpty($Filter.Trim())) { $verbString += " matching query '$($Filter.Trim())'" $request.Query = $Filter.Trim() } } if ($PSBoundParameters.Keys -notcontains 'Where_IsAMember') { if ($PSBoundParameters.Keys -contains 'Domain') { $verbString += " for domain '$Domain'" $request.Domain = $Domain } elseif ( -not [String]::IsNullOrEmpty($Script:PSGSuite.CustomerID)) { $verbString += " for customer '$($Script:PSGSuite.CustomerID)'" $request.Customer = $Script:PSGSuite.CustomerID } else { $verbString += " for customer 'my_customer'" $request.Customer = "my_customer" } } 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) $PageSize = $Limit } $request.MaxResults = $PageSize Write-Verbose $verbString [int]$i = 1 $overLimit = $false do { $result = $request.Execute() if ($null -ne $result.GroupsValue) { $result.GroupsValue | Add-Member -MemberType ScriptMethod -Name ToString -Value {$this.Email} -PassThru -Force } $request.PageToken = $result.NextPageToken [int]$retrieved = ($i + $result.GroupsValue.Count) - 1 Write-Verbose "Retrieved $retrieved groups..." if ($Limit -gt 0 -and $retrieved -eq $Limit) { Write-Verbose "Limit reached: $Limit" $overLimit = $true } elseif ($Limit -gt 0 -and ($retrieved + $PageSize) -gt $Limit) { $newPS = $Limit - $retrieved Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize,$newPS) $request.MaxResults = $newPS } [int]$i = $i + $result.GroupsValue.Count } until ($overLimit -or !$result.NextPageToken) } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } } Export-ModuleMember -Function 'Get-GSGroup' function Get-GSGroupAlias { <# .SYNOPSIS Gets the specified G SUite Group's aliases .DESCRIPTION Gets the specified G SUite Group's aliases .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 -Identity hr Gets the list of aliases for the group hr@domain.com #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Alias')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias('GroupEmail','Group','Email')] [String[]] $Identity ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.group' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { foreach ($G in $Identity) { try { 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 } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Get-GSGroupAlias' function Get-GSGroupMember { <# .SYNOPSIS Gets the group member list of a target group .DESCRIPTION Gets the group member list of a target group. Designed for parity with Get-ADGroupMember .PARAMETER Identity The email or unique ID of the target group .PARAMETER Member If specified, returns only the information for this member of the target group .PARAMETER Roles If specified, returns only the members of the specified role(s) .PARAMETER PageSize Page size of the result set .PARAMETER Limit The maximum amount of results you want returned. Exclude or set to 0 to return all results .EXAMPLE Get-GSGroupMember "admins@domain.com" -Roles Owner,Manager Returns the list of owners and managers of the group "admins@domain.com" #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Member')] [cmdletbinding(DefaultParameterSetName = "List")] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias('GroupEmail','Group','Email')] [String[]] $Identity, [parameter(Mandatory = $false,Position = 1,ParameterSetName = "Get")] [Alias("PrimaryEmail","UserKey","Mail","User","UserEmail","Members")] [ValidateNotNullOrEmpty()] [String[]] $Member, [parameter(Mandatory=$false,ParameterSetName = "List")] [ValidateSet("Owner","Manager","Member")] [String[]] $Roles, [parameter(Mandatory=$false,ParameterSetName = "List")] [ValidateRange(1,200)] [Int] $PageSize = 200, [parameter(Mandatory=$false,ParameterSetName = "List")] [Alias('First')] [Int] $Limit = 0 ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.group' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { switch ($PSCmdlet.ParameterSetName) { Get { foreach ($I in $Identity) { try { Resolve-Email ([ref]$I) -IsGroup foreach ($G in $Member) { 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 } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } List { foreach ($Id in $Identity) { try { 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) $PageSize = $Limit } $request.MaxResults = $PageSize if ($Roles) { Write-Verbose "Getting all members of group '$Id' in the following role(s): $($Roles -join ',')" $request.Roles = "$($Roles -join ',')" } else { Write-Verbose "Getting all members of group '$Id'" } [int]$i = 1 $overLimit = $false do { $result = $request.Execute() if ($null -ne $result.MembersValue) { $result.MembersValue | Add-Member -MemberType NoteProperty -Name 'Group' -Value $Id -PassThru | Add-Member -MemberType ScriptMethod -Name ToString -Value {$this.Email} -PassThru -Force } $request.PageToken = $result.NextPageToken [int]$retrieved = ($i + $result.MembersValue.Count) - 1 Write-Verbose "Retrieved $retrieved members..." if ($Limit -gt 0 -and $retrieved -eq $Limit) { Write-Verbose "Limit reached: $Limit" $overLimit = $true } elseif ($Limit -gt 0 -and ($retrieved + $PageSize) -gt $Limit) { $newPS = $Limit - $retrieved Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize,$newPS) $request.MaxResults = $newPS } [int]$i = $i + $result.MembersValue.Count } until ($overLimit -or !$result.NextPageToken) } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } } } Export-ModuleMember -Function 'Get-GSGroupMember' function Get-GSGroupSettings { <# .SYNOPSIS Gets a group's settings .DESCRIPTION Gets a group's settings .PARAMETER Identity 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 .EXAMPLE Get-GSGroupSettings admins Gets the group settings for admins@domain.com #> [OutputType('Google.Apis.Groupssettings.v1.Data.Groups')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias('GroupEmail','Group','Email')] [String[]] $Identity ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/apps.groups.settings' ServiceType = 'Google.Apis.Groupssettings.v1.GroupssettingsService' } $service = New-GoogleService @serviceParams } Process { try { foreach ($G in $Identity) { 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) $request.Alt = "Json" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Group' -Value $G -PassThru } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSGroupSettings' function New-GSGroup { <# .SYNOPSIS Creates a new Google Group .DESCRIPTION Creates a new Google Group .PARAMETER Email The desired email of the new group. If the group already exists, a GoogleApiException will be thrown. You can exclude the '@domain.com' to insert the Domain in the config .PARAMETER Name The name of the new group .PARAMETER Description The description of the new group .EXAMPLE New-GSGroup -Email appdev -Name "Application Developers" -Description "App Dev team members" Creates a new group named "Application Developers" with the email "appdev@domain.com" and description "App Dev team members" #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Group')] [cmdletbinding()] Param ( [parameter(Mandatory = $true)] [String] $Email, [parameter(Mandatory = $true)] [String] $Name, [parameter(Mandatory = $false)] [String] $Description ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.group' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { 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 { $body.$prop = $Email } Default { $body.$prop = $PSBoundParameters[$prop] } } } $request = $service.Groups.Insert($body) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'New-GSGroup' function New-GSGroupAlias { <# .SYNOPSIS Creates a new alias for a G Suite group .DESCRIPTION Creates a new alias for a G Suite 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 -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 ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias('GroupEmail','Group','Email')] [String] $Identity, [parameter(Mandatory = $true,Position = 1)] [String[]] $Alias ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.group' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { Resolve-Email ([ref]$Identity) -IsGroup foreach ($A in $Alias) { try { 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,$Identity) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'New-GSGroupAlias' function Remove-GSGroup { <# .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 ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias('GroupEmail','Group','Email')] [String[]] $Identity ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.group' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { foreach ($G in $Identity) { Resolve-Email ([ref]$G) -IsGroup if ($PSCmdlet.ShouldProcess("Removing group '$G'")) { Write-Verbose "Removing group '$G'" $request = $service.Groups.Delete($G) $request.Execute() Write-Verbose "Group '$G' has been successfully removed" } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Remove-GSGroup' function Remove-GSGroupAlias { <# .SYNOPSIS Removes an alias from a G Suite group .DESCRIPTION Removes an alias from a G Suite 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 -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,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias('GroupEmail','Group','Email')] [String] $Identity, [parameter(Mandatory = $true,Position = 1)] [String[]] $Alias ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.group' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { Resolve-Email ([ref]$Identity) -IsGroup foreach ($A in $Alias) { try { 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 '$Identity'" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Remove-GSGroupAlias' function Remove-GSGroupMember { <# .SYNOPSIS Removes members from a group .DESCRIPTION Removes members from a group .PARAMETER Identity The email or unique Id of the group to remove members from .PARAMETER Member The member or array of members to remove from the target group .EXAMPLE Remove-GSGroupMember -Identity admins -Member joe.smith,mark.taylor -Confirm:$false Removes members Joe Smith and Mark Taylor from the group admins@domain.com and skips asking for confirmation #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias('GroupEmail','Group','Email')] [String] $Identity, [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true,Position = 1)] [Alias("PrimaryEmail","UserKey","Mail","User","UserEmail","Members")] [ValidateNotNullOrEmpty()] [String[]] $Member ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.group' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { Resolve-Email ([ref]$Identity) -IsGroup foreach ($G in $Member) { try { 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) $request.Execute() Write-Verbose "Member '$G' has been successfully removed" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Remove-GSGroupMember' 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 Removes Joe Smith from the groups admins@domain.com and test_pool@domain.com #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail","User","UserEmail")] [String] $Identity, [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true,Position = 1)] [Alias('GroupEmail','Group','Email')] [String[]] $MemberOf ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.group' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { Resolve-Email ([ref]$Identity) foreach ($G in $MemberOf) { try { 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 '$Identity' has been successfully removed from group '$G'" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Remove-GSPrincipalGroupMembership' function Set-GSGroupSettings { <# .SYNOPSIS Hard-sets the settings for a group .DESCRIPTION Hard-sets the settings for a group .PARAMETER Identity The primary email or unique Id of the group to set .PARAMETER Name The new name of the group .PARAMETER Description The new description of the group .PARAMETER AllowExternalMembers Are external members allowed to join the group .PARAMETER AllowWebPosting If posting from web is allowed .PARAMETER ArchiveOnly If the group is archive only .PARAMETER CustomFooterText Custom footer text .PARAMETER CustomReplyTo Email address used when replying to a message if the replyTo property is set to REPLY_TO_CUSTOM .PARAMETER DefaultMessageDenyNotificationText Text of message sent when message is rejected to message's author Requires SendMessageDenyNotification to be true .PARAMETER EnableCollaborativeInbox Whether a collaborative inbox will remain turned on for the group .PARAMETER FavoriteRepliesOnTop If favorite replies should be displayed above other replies .PARAMETER IncludeCustomFooter Whether to include custom footer .PARAMETER IncludeInGlobalAddressList If this groups should be included in global address list or not .PARAMETER IsArchived If the contents of the group are archived .PARAMETER MembersCanPostAsTheGroup Can members post using the group email address .PARAMETER MessageModerationLevel Moderation level of incoming messages Available values are: * "MODERATE_ALL_MESSAGES" * "MODERATE_NON_MEMBERS" * "MODERATE_NEW_MEMBERS" * "MODERATE_NONE" -GApps Default .PARAMETER PrimaryLanguage The primary language for group in format of RFC 3066 Language Tag accepted by Google Mail .PARAMETER ReplyTo who the default reply should go to Available values are: * "REPLY_TO_CUSTOM" -Requires that CustomReplyTo must be set * "REPLY_TO_SENDER" * "REPLY_TO_LIST" * "REPLY_TO_OWNER" * "REPLY_TO_IGNORE" -GApps Default * "REPLY_TO_MANAGERS" .PARAMETER SendMessageDenyNotification Allows a member to be notified if the member's message to the group is denied by the group owner If true, the property defaultMessageDenyNotificationText is dependent on this property .PARAMETER SpamModerationLevel Specifies moderation levels for messages detected as spam Available values are: * "ALLOW" * "MODERATE" -GApps Default * "SILENTLY_MODERATE" * "REJECT" .PARAMETER WhoCanAssistContent Specifies who can moderate metadata Note: In March 2019, the whoCanModerateMembers, whoCanModerateContent, and whoCanAssistContent parent properties were Available values are: * "ALL_MEMBERS" * "OWNERS_AND_MANAGERS" * "MANAGERS_ONLY" * "OWNERS_ONLY" * "NONE" -GApps Default .PARAMETER WhoCanContactOwner Specifies who can contact the group owner Available values are: * "ALL_IN_DOMAIN_CAN_CONTACT" * "ALL_MANAGERS_CAN_CONTACT" * "ALL_MEMBERS_CAN_CONTACT" * "ANYONE_CAN_CONTACT" .PARAMETER WhoCanDiscoverGroup Specifies the set of users for whom this group is discoverable Available values are: * "ANYONE_CAN_DISCOVER" * "ALL_IN_DOMAIN_CAN_DISCOVER" -GApps Default * "ALL_MEMBERS_CAN_DISCOVER" .PARAMETER WhoCanJoin Permission to join group Available values are: * "ANYONE_CAN_JOIN" * "ALL_IN_DOMAIN_CAN_JOIN" * "INVITED_CAN_JOIN" * "CAN_REQUEST_TO_JOIN" .PARAMETER WhoCanLeaveGroup Specifies who can leave the group Available values are: * "ALL_MANAGERS_CAN_LEAVE" * "ALL_MEMBERS_CAN_LEAVE" -GApps Default * "NONE_CAN_LEAVE" .PARAMETER WhoCanModerateContent Specifies who can moderate content Note: In March 2019, the whoCanModerateMembers, whoCanModerateContent, and whoCanAssistContent parent properties were added to merge similar properties. Available values are: * "ALL_MEMBERS" * "OWNERS_AND_MANAGERS" -GApps Default * "OWNERS_ONLY" * "NONE" .PARAMETER WhoCanModerateMembers Specifies who can manage members Note: In March 2019, the whoCanModerateMembers, whoCanModerateContent, and whoCanAssistContent parent properties were added to merge similar properties. Available values are: * "ALL_MEMBERS" * "OWNERS_AND_MANAGERS" * "OWNERS_ONLY" -GApps Default * "NONE" .PARAMETER WhoCanPostMessage Permissions to post messages Available values are: * "NONE_CAN_POST" * "ALL_MANAGERS_CAN_POST" * "ALL_MEMBERS_CAN_POST" * "ALL_OWNERS_CAN_POST" * "ALL_IN_DOMAIN_CAN_POST" * "ANYONE_CAN_POST" .PARAMETER WhoCanViewGroup Permissions to view group messages Available values are: * "ANYONE_CAN_VIEW" * "ALL_IN_DOMAIN_CAN_VIEW" * "ALL_MEMBERS_CAN_VIEW" -GApps Default * "ALL_MANAGERS_CAN_VIEW" * "ALL_OWNERS_CAN_VIEW" .PARAMETER WhoCanViewMembership Permissions to view membership Available values are: * "ALL_IN_DOMAIN_CAN_VIEW" * "ALL_MEMBERS_CAN_VIEW" -GApps Default * "ALL_MANAGERS_CAN_VIEW" .EXAMPLE Set-GSGroupSettings admins,hr-notifications -AllowExternalMembers:$false -WhoCanPostMessage ALL_OWNERS_CAN_POST Sets the group settings for both admins@domain.com and hr-notifications@domain.com to deny external members and limit posting to only group owners #> [OutputType('Google.Apis.Groupssettings.v1.Data.Groups')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias('GroupEmail','Group')] [String[]] $Identity, [parameter(Mandatory = $false)] [ValidateScript( {$_.length -le 75})] [String] $Name, [parameter(Mandatory = $false)] [ValidateScript( {$_.length -le 300})] [String] $Description, [parameter(Mandatory = $false)] [Switch] $AllowExternalMembers, [parameter(Mandatory = $false)] [Switch] $AllowWebPosting, [parameter(Mandatory = $false)] [Switch] $ArchiveOnly, [parameter(Mandatory = $false)] [ValidateScript( {$_.length -le 1000})] [String] $CustomFooterText, [parameter(Mandatory = $false)] [String] $CustomReplyTo, [parameter(Mandatory = $false)] [Alias('MessageDenyNotificationText')] [ValidateScript( {$_.length -le 10000})] [String] $DefaultMessageDenyNotificationText, [parameter(Mandatory = $false)] [Switch] $EnableCollaborativeInbox, [parameter(Mandatory = $false)] [Switch] $FavoriteRepliesOnTop, [parameter(Mandatory = $false)] [Switch] $IncludeCustomFooter, [parameter(Mandatory = $false)] [Switch] $IncludeInGlobalAddressList, [parameter(Mandatory = $false)] [Switch] $IsArchived, [parameter(Mandatory = $false)] [Switch] $MembersCanPostAsTheGroup, [parameter(Mandatory = $false)] [ValidateSet("MODERATE_ALL_MESSAGES","MODERATE_NEW_MEMBERS","MODERATE_NONE","MODERATE_NON_MEMBERS")] [String] $MessageModerationLevel, [ValidateScript( {$_.length -le 5})] [String] $PrimaryLanguage, [parameter(Mandatory = $false)] [ValidateSet("REPLY_TO_CUSTOM","REPLY_TO_IGNORE","REPLY_TO_LIST","REPLY_TO_MANAGERS","REPLY_TO_OWNER","REPLY_TO_SENDER")] [String] $ReplyTo, [parameter(Mandatory = $false)] [Switch] $SendMessageDenyNotification, [parameter(Mandatory = $false)] [ValidateSet("ALLOW","MODERATE","SILENTLY_MODERATE","REJECT")] [String] $SpamModerationLevel, [parameter(Mandatory = $false)] [ValidateSet("ALL_MEMBERS","OWNERS_AND_MANAGERS","MANAGERS_ONLY","OWNERS_ONLY","NONE")] [String] $WhoCanAssistContent, [parameter(Mandatory = $false)] [ValidateSet("ALL_IN_DOMAIN_CAN_CONTACT","ALL_MANAGERS_CAN_CONTACT","ALL_MEMBERS_CAN_CONTACT","ANYONE_CAN_CONTACT")] [String] $WhoCanContactOwner, [parameter(Mandatory = $false)] [ValidateSet("ANYONE_CAN_DISCOVER","ALL_IN_DOMAIN_CAN_DISCOVER","ALL_MEMBERS_CAN_DISCOVER")] [String] $WhoCanDiscoverGroup, [parameter(Mandatory = $false)] [ValidateSet("ALL_IN_DOMAIN_CAN_JOIN","ANYONE_CAN_JOIN","CAN_REQUEST_TO_JOIN","INVITED_CAN_JOIN")] [String] $WhoCanJoin, [parameter(Mandatory = $false)] [ValidateSet("ALL_MANAGERS_CAN_LEAVE","ALL_MEMBERS_CAN_LEAVE","NONE_CAN_LEAVE")] [String] $WhoCanLeaveGroup, [parameter(Mandatory = $false)] [ValidateSet("ALL_MEMBERS","OWNERS_AND_MANAGERS","OWNERS_ONLY","NONE")] [String] $WhoCanModerateContent, [parameter(Mandatory = $false)] [ValidateSet("ALL_MEMBERS","OWNERS_AND_MANAGERS","OWNERS_ONLY","NONE")] [String] $WhoCanModerateMembers, [parameter(Mandatory = $false)] [ValidateSet("NONE_CAN_POST","ALL_MANAGERS_CAN_POST","ALL_MEMBERS_CAN_POST","ALL_OWNERS_CAN_POST","ALL_IN_DOMAIN_CAN_POST","ANYONE_CAN_POST")] [String] $WhoCanPostMessage, [parameter(Mandatory = $false)] [ValidateSet("ANYONE_CAN_VIEW","ALL_IN_DOMAIN_CAN_VIEW","ALL_MEMBERS_CAN_VIEW","ALL_MANAGERS_CAN_VIEW","ALL_OWNERS_CAN_VIEW")] [String] $WhoCanViewGroup, [parameter(Mandatory = $false)] [ValidateSet("ALL_IN_DOMAIN_CAN_VIEW","ALL_MANAGERS_CAN_VIEW","ALL_MEMBERS_CAN_VIEW")] [String] $WhoCanViewMembership ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/apps.groups.settings' ServiceType = 'Google.Apis.Groupssettings.v1.GroupssettingsService' } $service = New-GoogleService @serviceParams } Process { try { foreach ($G in $Identity) { 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 $_}) { switch ($prop) { MaxMessageBytes { $body.$prop = $PSBoundParameters[$prop] } Default { $body.$prop = if ($PSBoundParameters[$prop].ToString() -in @("True","False")) { $($PSBoundParameters[$prop]).ToString().ToLower() } else { $PSBoundParameters[$prop] } } } } $request = $service.Groups.Update($body,$G) $request.Alt = "Json" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Group' -Value $G -PassThru } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Set-GSGroupSettings' function Test-GSGroupMembership { <# .SYNOPSIS Checks if a Group has a specific member .DESCRIPTION Checks if a Group has a specific member .PARAMETER Identity The email of the group If only the email name-part is passed, the full email will be contstructed using the Domain from the active config .PARAMETER Member The user to confirm as a member of the Group .EXAMPLE Test-GSGroupMembership -Identity admins@domain.com -Member john@domain.com Tests if john@domain.com is a member of admins@domain.com #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.MembersHasMember')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias('GroupEmail','Group','Email')] [String] $Identity, [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true,Position = 1)] [Alias("PrimaryEmail","UserKey","Mail","User","UserEmail","Members")] [ValidateNotNullOrEmpty()] [String[]] $Member ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.group' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { Resolve-Email ([ref]$Identity) -IsGroup foreach ($mem in $Member) { try { 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 } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Test-GSGroupMembership' function Update-GSGroup { <# .SYNOPSIS Updates the specified groups information .DESCRIPTION Updates the specified groups information .PARAMETER Identity The primary email or unique Id of the group to update .PARAMETER Email The new email id of the group. The previous email will become an alias automatically .PARAMETER Name The new name of the group .PARAMETER Description The new description of the group .EXAMPLE Update-GSGroup -Identity myGroup -Email myNewGroupName Updates the email address of group 'myGroup@domain.com' with the email 'myNewGroupname@domain.com' #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Group')] [cmdletbinding()] Param ( [parameter(Mandatory = $true)] [String] $Identity, [parameter(Mandatory = $false)] [String] $Email, [parameter(Mandatory = $false)] [String] $Name, [parameter(Mandatory = $false)] [String] $Description ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.group' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { Resolve-Email ([ref]$Identity) -IsGroup Write-Verbose "Updating group '$Identity'" $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] } Default { $body.$prop = $PSBoundParameters[$prop] } } } $request = $service.Groups.Patch($body, $Identity) $request.Alt = "Json" $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Update-GSGroup' function Update-GSGroupMember { <# .SYNOPSIS Updates a group member's role and/or delivery preference .DESCRIPTION Updates a group member's role and/or delivery preference .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 .PARAMETER Role The role that you would like to update the members to Acceptable values are: * MEMBER * MANAGER * OWNER .PARAMETER DeliverySettings Defines mail delivery preferences of member Acceptable values are: * "ALL_MAIL": All messages, delivered as soon as they arrive. * "DAILY": No more than one message a day. * "DIGEST": Up to 25 messages bundled into a single message. * "DISABLED": Remove subscription. * "NONE": No messages. .EXAMPLE Get-GSGroupMember myGroup | Update-GSGroupMember -DeliverySettings ALL_MAIL Updates the delivery preference for all members of group 'myGroup@domain.com' to 'ALL_MAIL' #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Member')] [cmdletbinding()] Param ( [parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Alias('GroupEmail', 'Group')] [String] $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")] [String] $Role, [parameter(Mandatory = $false)] [ValidateSet("ALL_MAIL", "DAILY", "DIGEST", "DISABLED", "NONE")] [String] $DeliverySettings ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.group' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { Resolve-Email ([ref]$Identity) -IsGroup foreach ($U in $Member) { try { 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') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Update-GSGroupMember' function Update-GSGroupSettings { <# .SYNOPSIS Updates the settings for a group while retaining excluded, already existing settings .DESCRIPTION Updates the settings for a group while retaining excluded, already existing settings .PARAMETER Identity The primary email or unique Id of the group to update .PARAMETER Name The new name of the group .PARAMETER Description The new description of the group .PARAMETER ArchiveOnly If the group is archive only .PARAMETER AllowExternalMembers Are external members allowed to join the group .PARAMETER AllowGoogleCommunication Is google allowed to contact admins .PARAMETER AllowWebPosting If posting from web is allowed .PARAMETER CustomFooterText Custom footer text .PARAMETER CustomReplyToAddress Default email to which reply to any message should go .PARAMETER DefaultMessageDenyNotificationText Default message deny notification message .PARAMETER EnableCollaborativeInbox Specifies whether the collaborative inbox functionality will be turned on or off for the group. .PARAMETER IncludeCustomFooter Whether to include custom footer .PARAMETER IncludeInGlobalAddressList If this groups should be included in global address list or not .PARAMETER IsArchived If the contents of the group are archived .PARAMETER MaxMessageBytes Maximum message size allowed .PARAMETER MembersCanPostAsTheGroup Can members post using the group email address .PARAMETER MessageDisplayFont Default message display font Available values are: * "DEFAULT_FONT" * "FIXED_WIDTH_FONT" .PARAMETER MessageModerationLevel Moderation level for messages Available values are: * "MODERATE_ALL_MESSAGES" * "MODERATE_NON_MEMBERS" * "MODERATE_NEW_MEMBERS" * "MODERATE_NONE" .PARAMETER ReplyTo Who should the default reply to a message go to Available values are: * "REPLY_TO_CUSTOM" * "REPLY_TO_SENDER" * "REPLY_TO_LIST" * "REPLY_TO_OWNER" * "REPLY_TO_IGNORE" * "REPLY_TO_MANAGERS" .PARAMETER SendMessageDenyNotification Should the member be notified if his message is denied by owner .PARAMETER ShowInGroupDirectory Is the group listed in groups directory .PARAMETER SpamModerationLevel Moderation level for messages detected as spam Available values are: * "ALLOW" * "MODERATE" * "SILENTLY_MODERATE" * "REJECT" .PARAMETER WhoCanAdd Permissions to add members Available values are: * "ALL_MANAGERS_CAN_ADD" * "ALL_MEMBERS_CAN_ADD" * "NONE_CAN_ADD" .PARAMETER WhoCanContactOwner Permission to contact owner of the group via web UI Available values are: * "ANYONE_CAN_CONTACT" * "ALL_IN_DOMAIN_CAN_CONTACT" * "ALL_MEMBERS_CAN_CONTACT" * "ALL_MANAGERS_CAN_CONTACT" .PARAMETER WhoCanDiscoverGroup Specifies the set of users for whom this group is discoverable. Available values are: * "ANYONE_CAN_DISCOVER" * "ALL_IN_DOMAIN_CAN_DISCOVER" * "ALL_MEMBERS_CAN_DISCOVER" .PARAMETER WhoCanInvite Permissions to invite members. Available values are: * "ALL_MEMBERS_CAN_INVITE" * "ALL_MANAGERS_CAN_INVITE" * "NONE_CAN_INVITE" .PARAMETER WhoCanJoin Permissions to join the group. Available values are: * "ANYONE_CAN_JOIN" * "ALL_IN_DOMAIN_CAN_JOIN" * "INVITED_CAN_JOIN" * "CAN_REQUEST_TO_JOIN" .PARAMETER WhoCanLeaveGroup Permission to leave the group. Available values are: * "ALL_MANAGERS_CAN_LEAVE" * "ALL_MEMBERS_CAN_LEAVE" * "NONE_CAN_LEAVE" .PARAMETER WhoCanPostMessage Permissions to post messages to the group. Available values are: * "NONE_CAN_POST" * "ALL_MANAGERS_CAN_POST" * "ALL_MEMBERS_CAN_POST" * "ALL_OWNERS_CAN_POST" * "ALL_IN_DOMAIN_CAN_POST" * "ANYONE_CAN_POST" .PARAMETER WhoCanViewGroup Permissions to view group. Available values are: * "ANYONE_CAN_VIEW" * "ALL_IN_DOMAIN_CAN_VIEW" * "ALL_MEMBERS_CAN_VIEW" * "ALL_MANAGERS_CAN_VIEW" .PARAMETER WhoCanViewMembership Permissions to view membership. Available values are: * "ALL_IN_DOMAIN_CAN_VIEW" * "ALL_MEMBERS_CAN_VIEW" * "ALL_MANAGERS_CAN_VIEW" .EXAMPLE Updates-GSGroupSettings admins,hr-notifications -AllowExternalMembers:$false -WhoCanPostMessage ALL_OWNERS_CAN_POST Updates the group settings for both admins@domain.com and hr-notifications@domain.com to deny external members and limit posting to only group owners #> [OutputType('Google.Apis.Groupssettings.v1.Data.Groups')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias('GroupEmail','Group')] [String[]] $Identity, [parameter(Mandatory = $false)] [String] $Name, [parameter(Mandatory = $false)] [String] $Description, [parameter(Mandatory = $false)] [Switch] $ArchiveOnly, [parameter(Mandatory = $false)] [Switch] $AllowExternalMembers, [parameter(Mandatory = $false)] [Switch] $AllowGoogleCommunication, [parameter(Mandatory = $false)] [Switch] $AllowWebPosting, [parameter(Mandatory = $false)] [ValidateScript( {$_.length -le 1000})] [String] $CustomFooterText, [parameter(Mandatory = $false)] [String] $CustomReplyToAddress, [parameter(Mandatory = $false)] [Alias('MessageDenyNotificationText')] [ValidateScript( {$_.length -le 10000})] [String] $DefaultMessageDenyNotificationText, [parameter(Mandatory = $false)] [Switch] $EnableCollaborativeInbox, [parameter(Mandatory = $false)] [Switch] $IncludeCustomFooter, [parameter(Mandatory = $false)] [Switch] $IncludeInGlobalAddressList, [parameter(Mandatory = $false)] [Switch] $IsArchived, [parameter(Mandatory = $false)] [int] $MaxMessageBytes, [parameter(Mandatory = $false)] [Switch] $MembersCanPostAsTheGroup, [parameter(Mandatory = $false)] [ValidateSet("DEFAULT_FONT","FIXED_WIDTH_FONT")] [String] $MessageDisplayFont, [parameter(Mandatory = $false)] [ValidateSet("MODERATE_ALL_MESSAGES","MODERATE_NEW_MEMBERS","MODERATE_NONE","MODERATE_NON_MEMBERS")] [String] $MessageModerationLevel, [parameter(Mandatory = $false)] [ValidateSet("REPLY_TO_CUSTOM","REPLY_TO_IGNORE","REPLY_TO_LIST","REPLY_TO_MANAGERS","REPLY_TO_OWNER","REPLY_TO_SENDER")] [String] $ReplyTo, [parameter(Mandatory = $false)] [Switch] $SendMessageDenyNotification, [parameter(Mandatory = $false)] [Switch] $ShowInGroupDirectory, [parameter(Mandatory = $false)] [ValidateSet("ALLOW","MODERATE","SILENTLY_MODERATE","REJECT")] [String] $SpamModerationLevel, [parameter(Mandatory = $false)] [ValidateSet("ALL_MEMBERS_CAN_ADD","ALL_MANAGERS_CAN_ADD","NONE_CAN_ADD")] [String] $WhoCanAdd, [parameter(Mandatory = $false)] [ValidateSet("ALL_IN_DOMAIN_CAN_CONTACT","ALL_MANAGERS_CAN_CONTACT","ALL_MEMBERS_CAN_CONTACT","ANYONE_CAN_CONTACT")] [String] $WhoCanContactOwner, [parameter(Mandatory = $false)] [ValidateSet("ANYONE_CAN_DISCOVER","ALL_IN_DOMAIN_CAN_DISCOVER","ALL_MEMBERS_CAN_DISCOVER")] [String] $WhoCanDiscoverGroup, [parameter(Mandatory = $false)] [ValidateSet("ALL_MANAGERS_CAN_INVITE","ALL_MEMBERS_CAN_INVITE","NONE_CAN_INVITE")] [String] $WhoCanInvite, [parameter(Mandatory = $false)] [ValidateSet("ALL_IN_DOMAIN_CAN_JOIN","ANYONE_CAN_JOIN","CAN_REQUEST_TO_JOIN","INVITED_CAN_JOIN")] [String] $WhoCanJoin, [parameter(Mandatory = $false)] [ValidateSet("ALL_MANAGERS_CAN_LEAVE","ALL_MEMBERS_CAN_LEAVE","NONE_CAN_LEAVE")] [String] $WhoCanLeaveGroup, [parameter(Mandatory = $false)] [ValidateSet("ALL_IN_DOMAIN_CAN_POST","ALL_MANAGERS_CAN_POST","ALL_MEMBERS_CAN_POST","ANYONE_CAN_POST","NONE_CAN_POST")] [String] $WhoCanPostMessage, [parameter(Mandatory = $false)] [ValidateSet("ALL_IN_DOMAIN_CAN_VIEW","ALL_MANAGERS_CAN_VIEW","ALL_MEMBERS_CAN_VIEW","ANYONE_CAN_VIEW")] [String] $WhoCanViewGroup, [parameter(Mandatory = $false)] [ValidateSet("ALL_IN_DOMAIN_CAN_VIEW","ALL_MANAGERS_CAN_VIEW","ALL_MEMBERS_CAN_VIEW")] [String] $WhoCanViewMembership ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/apps.groups.settings' ServiceType = 'Google.Apis.Groupssettings.v1.GroupssettingsService' } $service = New-GoogleService @serviceParams } Process { try { foreach ($G in $Identity) { 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' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) { switch ($prop) { MaxMessageBytes { $body.$prop = $PSBoundParameters[$prop] } Default { $body.$prop = if ($PSBoundParameters[$prop].ToString() -in @("True","False")) { $($PSBoundParameters[$prop]).ToString().ToLower() } else { $PSBoundParameters[$prop] } } } } $request = $service.Groups.Patch($body,$G) $request.Alt = "Json" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Group' -Value $G -PassThru } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Update-GSGroupSettings' function Add-GSCalendarEventReminder { <# .SYNOPSIS Builds an EventReminder object to use when creating or updating a CalendarSubscription or CalendarEvent .DESCRIPTION Builds an EventReminder object to use when creating or updating a CalendarSubscription or CalendarEvent .PARAMETER Method The method used by this reminder. Defaults to email. Possible values are: * "email" - Reminders are sent via email. * "sms" - Reminders are sent via SMS. These are only available for G Suite customers. Requests to set SMS reminders for other account types are ignored. * "popup" - Reminders are sent via a UI popup. .PARAMETER Minutes Number of minutes before the start of the event when the reminder should trigger. Defaults to 30 minutes. Valid values are between 0 and 40320 (4 weeks in minutes). .PARAMETER InputObject Used for pipeline input of an existing IM object to strip the extra attributes and prevent errors .EXAMPLE #> [OutputType('Google.Apis.Calendar.v3.Data.EventReminder')] [CmdletBinding()] Param ( [Parameter(Mandatory, ParameterSetName = "Fields")] [ValidateSet('email','sms','popup')] [String] $Method, [Parameter(Mandatory, ParameterSetName = "Fields")] [Int] $Minutes, [Parameter(ValueFromPipeline, ParameterSetName = "InputObject")] [Google.Apis.Calendar.v3.Data.EventReminder[]] $InputObject ) Process { try { switch ($PSCmdlet.ParameterSetName) { Fields { New-Object 'Google.Apis.Calendar.v3.Data.EventReminder' -Property @{ Method = $Method Minutes = $Minutes } } InputObject { foreach ($iObj in $InputObject) { New-Object 'Google.Apis.Calendar.v3.Data.EventReminder' -Property @{ Method = $iObj.Method Minutes = $iObj.Minutes } } } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Add-GSCalendarEventReminder' function Add-GSCalendarNotification { <# .SYNOPSIS Builds an CalendarNotification object to use when creating or updating a CalendarSubscription .DESCRIPTION Builds an CalendarNotification object to use when creating or updating a CalendarSubscription .PARAMETER Method The method used to deliver the notification. Possible values are: * "email" - Reminders are sent via email. * "sms" - Reminders are sent via SMS. This value is read-only and is ignored on inserts and updates. SMS reminders are only available for G Suite customers. .PARAMETER Type The type of notification. Possible values are: * "eventCreation" - Notification sent when a new event is put on the calendar. * "eventChange" - Notification sent when an event is changed. * "eventCancellation" - Notification sent when an event is cancelled. * "eventResponse" - Notification sent when an event is changed. * "agenda" - An agenda with the events of the day (sent out in the morning). .PARAMETER InputObject Used for pipeline input of an existing IM object to strip the extra attributes and prevent errors .EXAMPLE #> [OutputType('Google.Apis.Calendar.v3.Data.CalendarNotification')] [CmdletBinding()] Param ( [Parameter(Mandatory, ParameterSetName = "Fields")] [ValidateSet('email','sms')] [String] $Method, [Parameter(Mandatory, ParameterSetName = "Fields")] [ValidateSet('eventCreation','eventChange','eventCancellation','eventResponse','agenda')] [String] $Type, [Parameter(ValueFromPipeline, ParameterSetName = "InputObject")] [Google.Apis.Calendar.v3.Data.CalendarNotification[]] $InputObject ) Process { try { switch ($PSCmdlet.ParameterSetName) { Fields { New-Object 'Google.Apis.Calendar.v3.Data.CalendarNotification' -Property @{ Method = $Method Type = $Type } } InputObject { foreach ($iObj in $InputObject) { New-Object 'Google.Apis.Calendar.v3.Data.CalendarNotification' -Property @{ Method = $iObj.Method Type = $iObj.Type } } } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Add-GSCalendarNotification' function Add-GSChatButton { <# .SYNOPSIS Creates a Chat Button widget to include in a section .DESCRIPTION Creates a Chat Button widget to include in a section .PARAMETER Text The Text for a Text Button .PARAMETER Icon The icon for the Image Button Available values are: * AIRPLANE * BOOKMARK * BUS * CAR * CLOCK * CONFIRMATION_NUMBER_ICON * DOLLAR * DESCRIPTION * EVENT_PERFORMER * EVENT_SEAT * FLIGHT_ARRIVAL * FLIGHT_DEPARTURE * HOTEL * HOTEL_ROOM_TYPE * INVITE * MAP_PIN * MEMBERSHIP * MULTIPLE_PEOPLE * OFFER * PERSON * PHONE * RESTAURANT_ICON * SHOPPING_CART * STAR * STORE * TICKET * TRAIN * VIDEO_CAMERA * VIDEO_PLAY .PARAMETER IconUrl The Url of the icon for the Image Button .PARAMETER OnClick The OnClick event that triggers when a user clicks the KeyValue You must use the function `Add-GSChatOnClick` to create OnClicks, otherwise this will throw a terminating error. .PARAMETER MessageSegment Any Chat message segment objects created with functions named `Add-GSChat*` passed through the pipeline or added directly to this parameter as values. .EXAMPLE Send-GSChatMessage -Text "Post job report:" -Cards $cards -Webhook (Get-GSChatWebhook JobReports) Sends a simple Chat message using the JobReports webhook .EXAMPLE Add-GSChatTextParagraph -Text "Guys...","We <b>NEED</b> to <i>stop</i> spending money on <b>crap</b>!" | Add-GSChatKeyValue -TopLabel "Chocolate Budget" -Content '$5.00' -Icon DOLLAR | Add-GSChatKeyValue -TopLabel "Actual Spending" -Content '$5,000,000!' -BottomLabel "WTF" -Icon AIRPLANE | Add-GSChatImage -ImageUrl "https://media.tenor.com/images/f78545a9b520ecf953578b4be220f26d/tenor.gif" -LinkImage | Add-GSChatCardSection -SectionHeader "Dollar bills, y'all" -OutVariable sect1 | Add-GSChatButton -Text "Launch nuke" -OnClick (Add-GSChatOnClick -Url "https://github.com/scrthq/PSGSuite") -Verbose -OutVariable button1 | Add-GSChatButton -Text "Unleash hounds" -OnClick (Add-GSChatOnClick -Url "https://admin.google.com/?hl=en&authuser=0") -Verbose -OutVariable button2 | Add-GSChatCardSection -SectionHeader "What should we do?" -OutVariable sect2 | Add-GSChatCard -HeaderTitle "Makin' moves with" -HeaderSubtitle "DEM GOODIES" -OutVariable card | Add-GSChatTextParagraph -Text "This message sent by <b>PSGSuite</b> via WebHook!" | Add-GSChatCardSection -SectionHeader "Additional Info" -OutVariable sect2 | Send-GSChatMessage -Text "Got that report, boss:" -FallbackText "Mistakes have been made..." -Webhook ReportRoom This example shows the pipeline capabilities of the Chat functions in PSGSuite. Starting from top to bottom: 1. Add a TextParagraph widget 2. Add a KeyValue with an icon 3. Add another KeyValue with a different icon 4. Add an image and create an OnClick event to open the image's URL by using the -LinkImage parameter 5. Add a new section to encapsulate the widgets sent through the pipeline before it 6. Add a TextButton that opens the PSGSuite GitHub repo when clicked 7. Add another TextButton that opens Google Admin Console when clicked 8. Wrap the 2 buttons in a new Section to divide the content 9. Wrap all widgets and sections in the pipeline so far in a Card 10. Add a new TextParagraph as a footer to the message 11. Wrap that TextParagraph in a new section 12. Send the message and include FallbackText that's displayed in the mobile notification. Since the final TextParagraph and Section are not followed by a new Card addition, Send-GSChatMessage will create a new Card just for the remaining segments then send the completed message via Webhook. The Webhook short-name is used to reference the full URL stored in the encrypted Config so it's not displayed in the actual script. .EXAMPLE Get-Service | Select-Object -First 5 | ForEach-Object { Add-GSChatKeyValue -TopLabel $_.DisplayName -Content $_.Status -BottomLabel $_.Name -Icon TICKET } | Add-GSChatCardSection -SectionHeader "Top 5 Services" | Send-GSChatMessage -Text "Service Report:" -FallbackText "Service Report" -Webhook Reports This gets the first 5 Services returned by Get-Service, creates KeyValue widgets for each, wraps it in a section with a header, then sends it to the Reports Webhook #> [CmdletBinding(DefaultParameterSetName = "Text")] Param ( [parameter(Mandatory = $true,Position = 0,ParameterSetName = "Text")] [String] $Text, [parameter(Mandatory = $true,ParameterSetName = "Icon")] [ValidateSet('AIRPLANE','BOOKMARK','BUS','CAR','CLOCK','CONFIRMATION_NUMBER_ICON','DOLLAR','DESCRIPTION','EMAIL','EVENT_PERFORMER','EVENT_SEAT','FLIGHT_ARRIVAL','FLIGHT_DEPARTURE','HOTEL','HOTEL_ROOM_TYPE','INVITE','MAP_PIN','MEMBERSHIP','MULTIPLE_PEOPLE','OFFER','PERSON','PHONE','RESTAURANT_ICON','SHOPPING_CART','STAR','STORE','TICKET','TRAIN','VIDEO_CAMERA','VIDEO_PLAY')] [String] $Icon, [parameter(Mandatory = $true,ParameterSetName = "IconUrl")] [String] $IconUrl, [parameter(Mandatory = $false)] [ValidateScript( { $allowedTypes = "PSGSuite.Chat.Message.Card.OnClick" if ([string]$($_.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") { $true } else { throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($_.PSTypeNames -join ", ")." } })] [Object] $OnClick, [parameter(Mandatory = $false,ValueFromPipeline = $true)] [Alias('InputObject')] [ValidateScript({ $allowedTypes = "PSGSuite.Chat.Message.Card.Section","PSGSuite.Chat.Message.Card","PSGSuite.Chat.Message.Card.CardAction","PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue" foreach ($item in $_) { if ([string]$($item.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") { $true } else { throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($item.PSTypeNames -join ", ")." } } })] [Object[]] $MessageSegment ) Begin { $widgetObject = @{ Webhook = @{ buttons = @() } SDK = (New-Object 'Google.Apis.HangoutsChat.v1.Data.WidgetMarkup' -Property @{ Buttons = (New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.Button]') }) } $widgetStack = @() switch ($PSCmdlet.ParameterSetName) { Text { $widgetObject['Webhook']['buttons'] += @{ textButton = @{ text = $Text onClick = $OnClick['Webhook'] } } $widgetObject['SDK'].Buttons.Add((New-Object 'Google.Apis.HangoutsChat.v1.Data.Button' -Property @{ TextButton = (New-Object 'Google.Apis.HangoutsChat.v1.Data.TextButton' -Property @{ Text = $Text OnClick = $OnClick['SDK'] }) })) | Out-Null } Icon { $widgetObject['Webhook']['buttons'] += @{ imageButton = @{ icon = $Icon onClick = $OnClick['Webhook'] } } $widgetObject['SDK'].Buttons.Add((New-Object 'Google.Apis.HangoutsChat.v1.Data.Button' -Property @{ ImageButton = (New-Object 'Google.Apis.HangoutsChat.v1.Data.ImageButton' -Property @{ Icon = $Icon OnClick = $OnClick['SDK'] }) })) | Out-Null } IconUrl { $widgetObject['Webhook']['buttons'] += @{ imageButton = @{ iconUrl = $IconUrl onClick = $OnClick['Webhook'] } } $widgetObject['SDK'].Buttons.Add((New-Object 'Google.Apis.HangoutsChat.v1.Data.Button' -Property @{ ImageButton = (New-Object 'Google.Apis.HangoutsChat.v1.Data.ImageButton' -Property @{ IconUrl = $IconUrl OnClick = $OnClick['SDK'] }) })) | Out-Null } } } Process { if ($MessageSegment) { foreach ($segment in $MessageSegment) { if ($segment.PSTypeNames[0] -in @("PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue")) { $widgetStack += $segment } else { $segment } } } } End { [void]$widgetObject.PSObject.TypeNames.Insert(0,'PSGSuite.Chat.Message.Card.Section.Button') if($widgetStack) { $newWidgetStack = @() for ($i = 0;$i -lt $widgetStack.Count;$i++) { if ($i -eq ($widgetStack.Count -1) -and ($widgetStack[$i].PSTypeNames[0] -eq 'PSGSuite.Chat.Message.Card.Section.Button')) { $widgetStack[$i]['Webhook']['buttons'] += $widgetObject['Webhook']['buttons'][0] $widgetStack[$i]['SDK'].Buttons.Add($widgetObject['SDK'].Buttons[0]) | Out-Null $newWidgetStack += $widgetStack[$i] } elseif ($i -eq ($widgetStack.Count -1)) { $newWidgetStack += $widgetStack[$i] $newWidgetStack += $widgetObject } else { $newWidgetStack += $widgetStack[$i] } } $newWidgetStack } else { $widgetObject } } } Export-ModuleMember -Function 'Add-GSChatButton' function Add-GSChatCard { <# .SYNOPSIS Creates a Chat Message Card .DESCRIPTION Creates a Chat Message Card .PARAMETER HeaderTitle The header title of the card .PARAMETER HeaderSubtitle The header subtitle of the card .PARAMETER HeaderImageStyle The header image style of the card Available values are: * IMAGE * AVATAR .PARAMETER HeaderImageUrl The header image URL of the card .PARAMETER CardActions The cardActions of the card. You must use the function `New-GSChatCardAction` to create cardActions, otherwise this will throw a terminating error. .PARAMETER MessageSegment Any Chat message segment objects created with functions named `Add-GSChat*` passed through the pipeline or added directly to this parameter as values. If section widgets are passed directly to this function, a new section without a SectionHeader will be created and the widgets will be added to it .EXAMPLE Send-GSChatMessage -Text "Post job report:" -Cards $cards -Webhook (Get-GSChatWebhook JobReports) Sends a simple Chat message using the JobReports webhook .EXAMPLE Add-GSChatTextParagraph -Text "Guys...","We <b>NEED</b> to <i>stop</i> spending money on <b>crap</b>!" | Add-GSChatKeyValue -TopLabel "Chocolate Budget" -Content '$5.00' -Icon DOLLAR | Add-GSChatKeyValue -TopLabel "Actual Spending" -Content '$5,000,000!' -BottomLabel "WTF" -Icon AIRPLANE | Add-GSChatImage -ImageUrl "https://media.tenor.com/images/f78545a9b520ecf953578b4be220f26d/tenor.gif" -LinkImage | Add-GSChatCardSection -SectionHeader "Dollar bills, y'all" -OutVariable sect1 | Add-GSChatButton -Text "Launch nuke" -OnClick (Add-GSChatOnClick -Url "https://github.com/scrthq/PSGSuite") -Verbose -OutVariable button1 | Add-GSChatButton -Text "Unleash hounds" -OnClick (Add-GSChatOnClick -Url "https://admin.google.com/?hl=en&authuser=0") -Verbose -OutVariable button2 | Add-GSChatCardSection -SectionHeader "What should we do?" -OutVariable sect2 | Add-GSChatCard -HeaderTitle "Makin' moves with" -HeaderSubtitle "DEM GOODIES" -OutVariable card | Add-GSChatTextParagraph -Text "This message sent by <b>PSGSuite</b> via WebHook!" | Add-GSChatCardSection -SectionHeader "Additional Info" -OutVariable sect2 | Send-GSChatMessage -Text "Got that report, boss:" -FallbackText "Mistakes have been made..." -Webhook ReportRoom This example shows the pipeline capabilities of the Chat functions in PSGSuite. Starting from top to bottom: 1. Add a TextParagraph widget 2. Add a KeyValue with an icon 3. Add another KeyValue with a different icon 4. Add an image and create an OnClick event to open the image's URL by using the -LinkImage parameter 5. Add a new section to encapsulate the widgets sent through the pipeline before it 6. Add a TextButton that opens the PSGSuite GitHub repo when clicked 7. Add another TextButton that opens Google Admin Console when clicked 8. Wrap the 2 buttons in a new Section to divide the content 9. Wrap all widgets and sections in the pipeline so far in a Card 10. Add a new TextParagraph as a footer to the message 11. Wrap that TextParagraph in a new section 12. Send the message and include FallbackText that's displayed in the mobile notification. Since the final TextParagraph and Section are not followed by a new Card addition, Send-GSChatMessage will create a new Card just for the remaining segments then send the completed message via Webhook. The Webhook short-name is used to reference the full URL stored in the encrypted Config so it's not displayed in the actual script. .EXAMPLE Get-Service | Select-Object -First 5 | ForEach-Object { Add-GSChatKeyValue -TopLabel $_.DisplayName -Content $_.Status -BottomLabel $_.Name -Icon TICKET } | Add-GSChatCardSection -SectionHeader "Top 5 Services" | Send-GSChatMessage -Text "Service Report:" -FallbackText "Service Report" -Webhook Reports This gets the first 5 Services returned by Get-Service, creates KeyValue widgets for each, wraps it in a section with a header, then sends it to the Reports Webhook #> Param ( [parameter(Mandatory = $false,Position = 0)] [String] $HeaderTitle, [parameter(Mandatory = $false)] [String] $HeaderSubtitle, [parameter(Mandatory = $false)] [ValidateSet('IMAGE','AVATAR')] [String] $HeaderImageStyle, [parameter(Mandatory = $false)] [String] $HeaderImageUrl, [parameter(Mandatory = $false)] [ValidateScript({ $allowedTypes = "PSGSuite.Chat.Message.Card.CardAction" foreach ($item in $_) { if ([string]$($item.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") { $true } else { throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($item.PSTypeNames -join ", ")." } } })] [Object[]] $CardActions, [parameter(Mandatory = $false,ValueFromPipeline = $true)] [Alias('InputObject')] [ValidateScript( { $allowedTypes = "PSGSuite.Chat.Message.Card.Section","PSGSuite.Chat.Message.Card","PSGSuite.Chat.Message.Card.CardAction","PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue" foreach ($item in $_) { if ([string]$($item.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") { $true } else { throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($item.PSTypeNames -join ", ")." } } })] [Object[]] $MessageSegment ) Begin { $cardObject = @{ Webhook = @{} SDK = (New-Object 'Google.Apis.HangoutsChat.v1.Data.Card') } $addlSectionWidgets = @() foreach ($key in $PSBoundParameters.Keys) { switch ($key) { HeaderTitle { if (!$cardObject['Webhook']['header']) { $cardObject['Webhook']['header'] = @{} } $cardObject['Webhook']['header']['title'] = $PSBoundParameters[$key] if (!$cardObject['SDK'].Header) { $cardObject['SDK'].Header = New-Object 'Google.Apis.HangoutsChat.v1.Data.CardHeader' } $cardObject['SDK'].Header.Title = $PSBoundParameters[$key] } HeaderSubtitle { if (!$cardObject['Webhook']['header']) { $cardObject['Webhook']['header'] = @{} } $cardObject['Webhook']['header']['subtitle'] = $PSBoundParameters[$key] if (!$cardObject['SDK'].Header) { $cardObject['SDK'].Header = New-Object 'Google.Apis.HangoutsChat.v1.Data.CardHeader' } $cardObject['SDK'].Header.Subtitle = $PSBoundParameters[$key] } HeaderImageStyle { if (!$cardObject['Webhook']['header']) { $cardObject['Webhook']['header'] = @{} } $cardObject['Webhook']['header']['imageStyle'] = $PSBoundParameters[$key] if (!$cardObject['SDK'].Header) { $cardObject['SDK'].Header = New-Object 'Google.Apis.HangoutsChat.v1.Data.CardHeader' } $cardObject['SDK'].Header.ImageStyle = $PSBoundParameters[$key] } HeaderImageUrl { if (!$cardObject['Webhook']['header']) { $cardObject['Webhook']['header'] = @{} } $cardObject['Webhook']['header']['imageUrl'] = $PSBoundParameters[$key] if (!$cardObject['SDK'].Header) { $cardObject['SDK'].Header = New-Object 'Google.Apis.HangoutsChat.v1.Data.CardHeader' } $cardObject['SDK'].Header.ImageUrl = $PSBoundParameters[$key] } CardActions { if (!$cardObject['Webhook']['cardActions']) { $cardObject['Webhook']['cardActions'] = @() } foreach ($cardAction in $CardActions) { $cardObject['Webhook']['cardActions'] += $cardAction['Webhook'] } if (!$cardObject['SDK'].CardActions) { $cardObject['SDK'].CardActions = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.CardAction]' } foreach ($cardAction in $CardActions) { $cardObject['SDK'].CardActions.Add($cardAction['SDK']) | Out-Null } } } } } Process { foreach ($segment in $MessageSegment) { if ($segment.PSTypeNames[0] -eq 'PSGSuite.Chat.Message.Card') { $segment } elseif ($segment.PSTypeNames[0] -in @("PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue")) { $addlSectionWidgets += $segment } elseif ($segment.PSTypeNames[0] -eq 'PSGSuite.Chat.Message.Card.CardAction') { if (!$cardObject['Webhook']['cardActions']) { $cardObject['Webhook']['cardActions'] = @() } $cardObject['Webhook']['cardActions'] += $segment['Webhook'] if (!$cardObject['SDK'].CardActions) { $cardObject['SDK'].CardActions = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.CardAction]' } $cardObject['SDK'].CardActions.Add($segment['SDK']) | Out-Null } elseif ($segment.PSTypeNames[0] -eq 'PSGSuite.Chat.Message.Card.Section') { if (!$cardObject['Webhook']['sections']) { $cardObject['Webhook']['sections'] = @() } $cardObject['Webhook']['sections'] += $segment['Webhook'] if (!$cardObject['SDK'].Sections) { $cardObject['SDK'].Sections = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.Section]' } $cardObject['SDK'].Sections.Add($segment['SDK']) | Out-Null } } } End { if($addlSectionWidgets) { $newWidgetStack = @() for ($i = 0;$i -lt $addlSectionWidgets.Count;$i++) { if ($newWidgetStack -and ($addlSectionWidgets[$i].PSTypeNames[0] -eq 'PSGSuite.Chat.Message.Card.Section.Button') -and ($newWidgetStack[-1].PSTypeNames[0] -eq 'PSGSuite.Chat.Message.Card.Section.Button')) { $newWidgetStack[-1]['Webhook']['buttons'] += $addlSectionWidgets[$i]['Webhook']['buttons'][0] $newWidgetStack[-1]['SDK'].Buttons.Add($addlSectionWidgets[$i]['SDK'].Buttons[0]) | Out-Null } else { $newWidgetStack += $addlSectionWidgets[$i] } } $addlSection = $newWidgetStack | Add-GSChatCardSection if (!$cardObject['Webhook']['sections']) { $cardObject['Webhook']['sections'] = @() } $cardObject['Webhook']['sections'] += $addlSection['Webhook'] if (!$cardObject['SDK'].Sections) { $cardObject['SDK'].Sections = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.Section]' } $cardObject['SDK'].Sections.Add($addlSection['SDK']) | Out-Null } [void]$cardObject.PSObject.TypeNames.Insert(0,'PSGSuite.Chat.Message.Card') return $cardObject } } Export-ModuleMember -Function 'Add-GSChatCard' function Add-GSChatCardAction { <# .SYNOPSIS Creates a Chat CardAction .DESCRIPTION Creates a Chat CardAction .PARAMETER ActionLabel The label used to be displayed in the action menu item. .PARAMETER OnClick The OnClick event that triggers when a user clicks the CardAction You must use the function `Add-GSChatOnClick` to create OnClicks, otherwise this will throw a terminating error. .PARAMETER MessageSegment Any Chat message segment objects created with functions named `Add-GSChat*` passed through the pipeline or added directly to this parameter as values. .EXAMPLE Send-GSChatMessage -Text "Post job report:" -Cards $cards -Webhook (Get-GSChatWebhook JobReports) Sends a simple Chat message using the JobReports webhook .EXAMPLE Add-GSChatTextParagraph -Text "Guys...","We <b>NEED</b> to <i>stop</i> spending money on <b>crap</b>!" | Add-GSChatKeyValue -TopLabel "Chocolate Budget" -Content '$5.00' -Icon DOLLAR | Add-GSChatKeyValue -TopLabel "Actual Spending" -Content '$5,000,000!' -BottomLabel "WTF" -Icon AIRPLANE | Add-GSChatImage -ImageUrl "https://media.tenor.com/images/f78545a9b520ecf953578b4be220f26d/tenor.gif" -LinkImage | Add-GSChatCardSection -SectionHeader "Dollar bills, y'all" -OutVariable sect1 | Add-GSChatButton -Text "Launch nuke" -OnClick (Add-GSChatOnClick -Url "https://github.com/scrthq/PSGSuite") -Verbose -OutVariable button1 | Add-GSChatButton -Text "Unleash hounds" -OnClick (Add-GSChatOnClick -Url "https://admin.google.com/?hl=en&authuser=0") -Verbose -OutVariable button2 | Add-GSChatCardSection -SectionHeader "What should we do?" -OutVariable sect2 | Add-GSChatCard -HeaderTitle "Makin' moves with" -HeaderSubtitle "DEM GOODIES" -OutVariable card | Add-GSChatTextParagraph -Text "This message sent by <b>PSGSuite</b> via WebHook!" | Add-GSChatCardSection -SectionHeader "Additional Info" -OutVariable sect2 | Send-GSChatMessage -Text "Got that report, boss:" -FallbackText "Mistakes have been made..." -Webhook ReportRoom This example shows the pipeline capabilities of the Chat functions in PSGSuite. Starting from top to bottom: 1. Add a TextParagraph widget 2. Add a KeyValue with an icon 3. Add another KeyValue with a different icon 4. Add an image and create an OnClick event to open the image's URL by using the -LinkImage parameter 5. Add a new section to encapsulate the widgets sent through the pipeline before it 6. Add a TextButton that opens the PSGSuite GitHub repo when clicked 7. Add another TextButton that opens Google Admin Console when clicked 8. Wrap the 2 buttons in a new Section to divide the content 9. Wrap all widgets and sections in the pipeline so far in a Card 10. Add a new TextParagraph as a footer to the message 11. Wrap that TextParagraph in a new section 12. Send the message and include FallbackText that's displayed in the mobile notification. Since the final TextParagraph and Section are not followed by a new Card addition, Send-GSChatMessage will create a new Card just for the remaining segments then send the completed message via Webhook. The Webhook short-name is used to reference the full URL stored in the encrypted Config so it's not displayed in the actual script. .EXAMPLE Get-Service | Select-Object -First 5 | ForEach-Object { Add-GSChatKeyValue -TopLabel $_.DisplayName -Content $_.Status -BottomLabel $_.Name -Icon TICKET } | Add-GSChatCardSection -SectionHeader "Top 5 Services" | Send-GSChatMessage -Text "Service Report:" -FallbackText "Service Report" -Webhook Reports This gets the first 5 Services returned by Get-Service, creates KeyValue widgets for each, wraps it in a section with a header, then sends it to the Reports Webhook #> Param ( [parameter(Mandatory = $true,Position = 0)] [String] $ActionLabel, [parameter(Mandatory = $true,Position = 1)] [ValidateScript({ $allowedTypes = "PSGSuite.Chat.Message.Card.OnClick" if ([string]$($_.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") { $true } else { throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($_.PSTypeNames -join ", ")." } })] [Object] $OnClick, [parameter(Mandatory = $false,ValueFromPipeline = $true)] [Alias('InputObject')] [ValidateScript({ $allowedTypes = "PSGSuite.Chat.Message.Card.Section","PSGSuite.Chat.Message.Card","PSGSuite.Chat.Message.Card.CardAction","PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue" foreach ($item in $_) { if ([string]$($item.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") { $true } else { throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($item.PSTypeNames -join ", ")." } } })] [Object[]] $MessageSegment ) Begin { $cardActionObject = @{ Webhook = @{ actionLabel = $ActionLabel onClick = $OnClick['Webhook'] } SDK = (New-Object 'Google.Apis.HangoutsChat.v1.Data.CardAction' -Property @{ ActionLabel = $ActionLabel OnClick = $OnClick['SDK'] }) } $widgetStack = @() } Process { foreach ($segment in $MessageSegment) { if ($segment.PSTypeNames[0] -in @("PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue")) { $widgetStack += $segment } else { $segment } } } End { [void]$cardActionObject.PSObject.TypeNames.Insert(0,'PSGSuite.Chat.Message.Card.CardAction') if($widgetStack) { $widgetStack += $cardActionObject $widgetStack } else { $cardActionObject } } } Export-ModuleMember -Function 'Add-GSChatCardAction' function Add-GSChatCardSection { <# .SYNOPSIS Creates a Chat Message Card Section .DESCRIPTION Creates a Chat Message Card Section .PARAMETER SectionHeader The header title of the section .PARAMETER MessageSegment Any Chat message segment objects created with functions named `Add-GSChat*` passed through the pipeline or added directly to this parameter as values. .EXAMPLE Send-GSChatMessage -Text "Post job report:" -Cards $cards -Webhook (Get-GSChatWebhook JobReports) Sends a simple Chat message using the JobReports webhook .EXAMPLE Add-GSChatTextParagraph -Text "Guys...","We <b>NEED</b> to <i>stop</i> spending money on <b>crap</b>!" | Add-GSChatKeyValue -TopLabel "Chocolate Budget" -Content '$5.00' -Icon DOLLAR | Add-GSChatKeyValue -TopLabel "Actual Spending" -Content '$5,000,000!' -BottomLabel "WTF" -Icon AIRPLANE | Add-GSChatImage -ImageUrl "https://media.tenor.com/images/f78545a9b520ecf953578b4be220f26d/tenor.gif" -LinkImage | Add-GSChatCardSection -SectionHeader "Dollar bills, y'all" -OutVariable sect1 | Add-GSChatButton -Text "Launch nuke" -OnClick (Add-GSChatOnClick -Url "https://github.com/scrthq/PSGSuite") -Verbose -OutVariable button1 | Add-GSChatButton -Text "Unleash hounds" -OnClick (Add-GSChatOnClick -Url "https://admin.google.com/?hl=en&authuser=0") -Verbose -OutVariable button2 | Add-GSChatCardSection -SectionHeader "What should we do?" -OutVariable sect2 | Add-GSChatCard -HeaderTitle "Makin' moves with" -HeaderSubtitle "DEM GOODIES" -OutVariable card | Add-GSChatTextParagraph -Text "This message sent by <b>PSGSuite</b> via WebHook!" | Add-GSChatCardSection -SectionHeader "Additional Info" -OutVariable sect2 | Send-GSChatMessage -Text "Got that report, boss:" -FallbackText "Mistakes have been made..." -Webhook ReportRoom This example shows the pipeline capabilities of the Chat functions in PSGSuite. Starting from top to bottom: 1. Add a TextParagraph widget 2. Add a KeyValue with an icon 3. Add another KeyValue with a different icon 4. Add an image and create an OnClick event to open the image's URL by using the -LinkImage parameter 5. Add a new section to encapsulate the widgets sent through the pipeline before it 6. Add a TextButton that opens the PSGSuite GitHub repo when clicked 7. Add another TextButton that opens Google Admin Console when clicked 8. Wrap the 2 buttons in a new Section to divide the content 9. Wrap all widgets and sections in the pipeline so far in a Card 10. Add a new TextParagraph as a footer to the message 11. Wrap that TextParagraph in a new section 12. Send the message and include FallbackText that's displayed in the mobile notification. Since the final TextParagraph and Section are not followed by a new Card addition, Send-GSChatMessage will create a new Card just for the remaining segments then send the completed message via Webhook. The Webhook short-name is used to reference the full URL stored in the encrypted Config so it's not displayed in the actual script. .EXAMPLE Get-Service | Select-Object -First 5 | ForEach-Object { Add-GSChatKeyValue -TopLabel $_.DisplayName -Content $_.Status -BottomLabel $_.Name -Icon TICKET } | Add-GSChatCardSection -SectionHeader "Top 5 Services" | Send-GSChatMessage -Text "Service Report:" -FallbackText "Service Report" -Webhook Reports This gets the first 5 Services returned by Get-Service, creates KeyValue widgets for each, wraps it in a section with a header, then sends it to the Reports Webhook #> Param ( [parameter(Mandatory = $false,Position = 0)] [String] $SectionHeader, [parameter(Mandatory = $true,ValueFromPipeline = $true)] [Alias('InputObject')] [ValidateScript({ $allowedTypes = "PSGSuite.Chat.Message.Card.Section","PSGSuite.Chat.Message.Card","PSGSuite.Chat.Message.Card.CardAction","PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue" foreach ($item in $_) { if ([string]$($item.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") { $true } else { throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($item.PSTypeNames -join ", ")." } } })] [Object[]] $MessageSegment ) Begin { $sectionObject = @{ Webhook = @{ widgets = @() } SDK = (New-Object 'Google.Apis.HangoutsChat.v1.Data.Section' -Property @{ Widgets = (New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.WidgetMarkup]') }) } if ($PSBoundParameters.Keys -contains 'SectionHeader') { $sectionObject['Webhook']['header'] = $SectionHeader $sectionObject['SDK'].Header = $SectionHeader } } Process { foreach ($segment in $MessageSegment) { if ($segment.PSTypeNames[0] -in @("PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue")) { $sectionObject['Webhook']['widgets'] += $segment['Webhook'] $sectionObject['SDK'].Widgets.Add($segment['SDK']) } else { $segment } } } End { [void]$sectionObject.PSObject.TypeNames.Insert(0,'PSGSuite.Chat.Message.Card.Section') return $sectionObject } } Export-ModuleMember -Function 'Add-GSChatCardSection' function Add-GSChatImage { <# .SYNOPSIS Creates a Chat Image widget to include in a section .DESCRIPTION Creates a Chat Image widget to include in a section .PARAMETER ImageUrl The Url of the Image .PARAMETER AspectRatio The AspectRatio of the Image .PARAMETER LinkImage If $true, automatically creates the OnClick event for the image to open the image URL .PARAMETER OnClick The OnClick event that triggers when a user clicks the KeyValue You must use the function `Add-GSChatOnClick` to create OnClicks, otherwise this will throw a terminating error. .PARAMETER MessageSegment Any Chat message segment objects created with functions named `Add-GSChat*` passed through the pipeline or added directly to this parameter as values. .EXAMPLE Send-GSChatMessage -Text "Post job report:" -Cards $cards -Webhook (Get-GSChatWebhook JobReports) Sends a simple Chat message using the JobReports webhook .EXAMPLE Add-GSChatTextParagraph -Text "Guys...","We <b>NEED</b> to <i>stop</i> spending money on <b>crap</b>!" | Add-GSChatKeyValue -TopLabel "Chocolate Budget" -Content '$5.00' -Icon DOLLAR | Add-GSChatKeyValue -TopLabel "Actual Spending" -Content '$5,000,000!' -BottomLabel "WTF" -Icon AIRPLANE | Add-GSChatImage -ImageUrl "https://media.tenor.com/images/f78545a9b520ecf953578b4be220f26d/tenor.gif" -LinkImage | Add-GSChatCardSection -SectionHeader "Dollar bills, y'all" -OutVariable sect1 | Add-GSChatButton -Text "Launch nuke" -OnClick (Add-GSChatOnClick -Url "https://github.com/scrthq/PSGSuite") -Verbose -OutVariable button1 | Add-GSChatButton -Text "Unleash hounds" -OnClick (Add-GSChatOnClick -Url "https://admin.google.com/?hl=en&authuser=0") -Verbose -OutVariable button2 | Add-GSChatCardSection -SectionHeader "What should we do?" -OutVariable sect2 | Add-GSChatCard -HeaderTitle "Makin' moves with" -HeaderSubtitle "DEM GOODIES" -OutVariable card | Add-GSChatTextParagraph -Text "This message sent by <b>PSGSuite</b> via WebHook!" | Add-GSChatCardSection -SectionHeader "Additional Info" -OutVariable sect2 | Send-GSChatMessage -Text "Got that report, boss:" -FallbackText "Mistakes have been made..." -Webhook ReportRoom This example shows the pipeline capabilities of the Chat functions in PSGSuite. Starting from top to bottom: 1. Add a TextParagraph widget 2. Add a KeyValue with an icon 3. Add another KeyValue with a different icon 4. Add an image and create an OnClick event to open the image's URL by using the -LinkImage parameter 5. Add a new section to encapsulate the widgets sent through the pipeline before it 6. Add a TextButton that opens the PSGSuite GitHub repo when clicked 7. Add another TextButton that opens Google Admin Console when clicked 8. Wrap the 2 buttons in a new Section to divide the content 9. Wrap all widgets and sections in the pipeline so far in a Card 10. Add a new TextParagraph as a footer to the message 11. Wrap that TextParagraph in a new section 12. Send the message and include FallbackText that's displayed in the mobile notification. Since the final TextParagraph and Section are not followed by a new Card addition, Send-GSChatMessage will create a new Card just for the remaining segments then send the completed message via Webhook. The Webhook short-name is used to reference the full URL stored in the encrypted Config so it's not displayed in the actual script. .EXAMPLE Get-Service | Select-Object -First 5 | ForEach-Object { Add-GSChatKeyValue -TopLabel $_.DisplayName -Content $_.Status -BottomLabel $_.Name -Icon TICKET } | Add-GSChatCardSection -SectionHeader "Top 5 Services" | Send-GSChatMessage -Text "Service Report:" -FallbackText "Service Report" -Webhook Reports This gets the first 5 Services returned by Get-Service, creates KeyValue widgets for each, wraps it in a section with a header, then sends it to the Reports Webhook #> [CmdletBinding(DefaultParameterSetName = "LinkImage")] Param ( [parameter(Mandatory = $true)] [String] $ImageUrl, [parameter(Mandatory = $false)] [Double] $AspectRatio, [parameter(Mandatory = $false,ParameterSetName = "LinkImage")] [Switch] $LinkImage, [parameter(Mandatory = $false,ParameterSetName = "OnClick")] [ValidateScript( { $allowedTypes = "PSGSuite.Chat.Message.Card.OnClick" if ([string]$($_.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") { $true } else { throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($_.PSTypeNames -join ", ")." } })] [Object] $OnClick, [parameter(Mandatory = $false,ValueFromPipeline = $true)] [Alias('InputObject')] [ValidateScript({ $allowedTypes = "PSGSuite.Chat.Message.Card.Section","PSGSuite.Chat.Message.Card","PSGSuite.Chat.Message.Card.CardAction","PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue" foreach ($item in $_) { if ([string]$($item.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") { $true } else { throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($item.PSTypeNames -join ", ")." } } })] [Object[]] $MessageSegment ) Begin { $widgetObject = @{ Webhook = @{ image = @{} } SDK = (New-Object 'Google.Apis.HangoutsChat.v1.Data.WidgetMarkup' -Property @{ Image = (New-Object 'Google.Apis.HangoutsChat.v1.Data.Image') }) } $widgetStack = @() foreach ($key in $PSBoundParameters.Keys) { switch ($key) { ImageUrl { $widgetObject['Webhook']['image']['imageUrl'] = $PSBoundParameters[$key] $widgetObject['SDK'].Image.ImageUrl = $PSBoundParameters[$key] } AspectRatio { $widgetObject['Webhook']['image']['aspectRatio'] = $PSBoundParameters[$key] $widgetObject['SDK'].Image.AspectRatio = $PSBoundParameters[$key] } OnClick { $widgetObject['Webhook']['image']['onClick'] = $PSBoundParameters[$key]['Webhook'] $widgetObject['SDK'].Image.OnClick = $PSBoundParameters[$key]['SDK'] } } } if ($LinkImage) { $newOnClick = Add-GSChatOnClick -Url $ImageUrl $widgetObject['Webhook']['image']['onClick'] = $newOnClick['Webhook'] $widgetObject['SDK'].Image.OnClick = $newOnClick['SDK'] } } Process { foreach ($segment in $MessageSegment) { if ($segment.PSTypeNames[0] -in @("PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue")) { $widgetStack += $segment } else { $segment } } } End { [void]$widgetObject.PSObject.TypeNames.Insert(0,'PSGSuite.Chat.Message.Card.Section.Image') if ($widgetStack) { $widgetStack += $widgetObject $widgetStack } else { $widgetObject } } } Export-ModuleMember -Function 'Add-GSChatImage' function Add-GSChatKeyValue { <# .SYNOPSIS Creates a Chat KeyValue widget to include in a section .DESCRIPTION Creates a Chat KeyValue widget to include in a section .PARAMETER TopLabel The TopLabel for the KeyValue .PARAMETER Content The Content for the KeyValue .PARAMETER BottomLabel The BottomLabel for the KeyValue .PARAMETER Icon The icon to display next to the KeyValue Available values are: * AIRPLANE * BOOKMARK * BUS * CAR * CLOCK * CONFIRMATION_NUMBER_ICON * DOLLAR * DESCRIPTION * EVENT_PERFORMER * EVENT_SEAT * FLIGHT_ARRIVAL * FLIGHT_DEPARTURE * HOTEL * HOTEL_ROOM_TYPE * INVITE * MAP_PIN * MEMBERSHIP * MULTIPLE_PEOPLE * OFFER * PERSON * PHONE * RESTAURANT_ICON * SHOPPING_CART * STAR * STORE * TICKET * TRAIN * VIDEO_CAMERA * VIDEO_PLAY .PARAMETER IconUrl The Url of the icon to display next to the KeyValue .PARAMETER ContentMultiline Whether the content of the KeyValue is multiline or not .PARAMETER OnClick The OnClick event that triggers when a user clicks the KeyValue You must use the function `Add-GSChatOnClick` to create OnClicks, otherwise this will throw a terminating error. .PARAMETER Button A button to add to the KeyValue You must use the function `Add-GSChatButton` to create Buttons, otherwise this will throw a terminating error. .PARAMETER MessageSegment Any Chat message segment objects created with functions named `Add-GSChat*` passed through the pipeline or added directly to this parameter as values. .EXAMPLE Send-GSChatMessage -Text "Post job report:" -Cards $cards -Webhook (Get-GSChatWebhook JobReports) Sends a simple Chat message using the JobReports webhook .EXAMPLE Add-GSChatTextParagraph -Text "Guys...","We <b>NEED</b> to <i>stop</i> spending money on <b>crap</b>!" | Add-GSChatKeyValue -TopLabel "Chocolate Budget" -Content '$5.00' -Icon DOLLAR | Add-GSChatKeyValue -TopLabel "Actual Spending" -Content '$5,000,000!' -BottomLabel "WTF" -Icon AIRPLANE | Add-GSChatImage -ImageUrl "https://media.tenor.com/images/f78545a9b520ecf953578b4be220f26d/tenor.gif" -LinkImage | Add-GSChatCardSection -SectionHeader "Dollar bills, y'all" -OutVariable sect1 | Add-GSChatButton -Text "Launch nuke" -OnClick (Add-GSChatOnClick -Url "https://github.com/scrthq/PSGSuite") -Verbose -OutVariable button1 | Add-GSChatButton -Text "Unleash hounds" -OnClick (Add-GSChatOnClick -Url "https://admin.google.com/?hl=en&authuser=0") -Verbose -OutVariable button2 | Add-GSChatCardSection -SectionHeader "What should we do?" -OutVariable sect2 | Add-GSChatCard -HeaderTitle "Makin' moves with" -HeaderSubtitle "DEM GOODIES" -OutVariable card | Add-GSChatTextParagraph -Text "This message sent by <b>PSGSuite</b> via WebHook!" | Add-GSChatCardSection -SectionHeader "Additional Info" -OutVariable sect2 | Send-GSChatMessage -Text "Got that report, boss:" -FallbackText "Mistakes have been made..." -Webhook ReportRoom This example shows the pipeline capabilities of the Chat functions in PSGSuite. Starting from top to bottom: 1. Add a TextParagraph widget 2. Add a KeyValue with an icon 3. Add another KeyValue with a different icon 4. Add an image and create an OnClick event to open the image's URL by using the -LinkImage parameter 5. Add a new section to encapsulate the widgets sent through the pipeline before it 6. Add a TextButton that opens the PSGSuite GitHub repo when clicked 7. Add another TextButton that opens Google Admin Console when clicked 8. Wrap the 2 buttons in a new Section to divide the content 9. Wrap all widgets and sections in the pipeline so far in a Card 10. Add a new TextParagraph as a footer to the message 11. Wrap that TextParagraph in a new section 12. Send the message and include FallbackText that's displayed in the mobile notification. Since the final TextParagraph and Section are not followed by a new Card addition, Send-GSChatMessage will create a new Card just for the remaining segments then send the completed message via Webhook. The Webhook short-name is used to reference the full URL stored in the encrypted Config so it's not displayed in the actual script. .EXAMPLE Get-Service | Select-Object -First 5 | ForEach-Object { Add-GSChatKeyValue -TopLabel $_.DisplayName -Content $_.Status -BottomLabel $_.Name -Icon TICKET } | Add-GSChatCardSection -SectionHeader "Top 5 Services" | Send-GSChatMessage -Text "Service Report:" -FallbackText "Service Report" -Webhook Reports This gets the first 5 Services returned by Get-Service, creates KeyValue widgets for each, wraps it in a section with a header, then sends it to the Reports Webhook #> [CmdletBinding(DefaultParameterSetName = "Icon")] Param ( [parameter(Mandatory = $false)] [String] $TopLabel, [parameter(Mandatory = $false)] [String] $Content, [parameter(Mandatory = $false)] [String] $BottomLabel, [parameter(Mandatory = $false,ParameterSetName = "Icon")] [ValidateSet('AIRPLANE','BOOKMARK','BUS','CAR','CLOCK','CONFIRMATION_NUMBER_ICON','DOLLAR','DESCRIPTION','EMAIL','EVENT_PERFORMER','EVENT_SEAT','FLIGHT_ARRIVAL','FLIGHT_DEPARTURE','HOTEL','HOTEL_ROOM_TYPE','INVITE','MAP_PIN','MEMBERSHIP','MULTIPLE_PEOPLE','OFFER','PERSON','PHONE','RESTAURANT_ICON','SHOPPING_CART','STAR','STORE','TICKET','TRAIN','VIDEO_CAMERA','VIDEO_PLAY')] [String] $Icon, [parameter(Mandatory = $false,ParameterSetName = "IconUrl")] [String] $IconUrl, [parameter(Mandatory = $false)] [Switch] $ContentMultiline, [parameter(Mandatory = $false)] [ValidateScript( { $allowedTypes = "PSGSuite.Chat.Message.Card.OnClick" if ([string]$($_.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") { $true } else { throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($_.PSTypeNames -join ", ")." } })] [Object] $OnClick, [parameter(Mandatory = $false)] [ValidateScript( { $allowedTypes = "PSGSuite.Chat.Message.Card.Section.Button" if ([string]$($_.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") { $true } else { throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($_.PSTypeNames -join ", ")." } })] [Object] $Button, [parameter(Mandatory = $false,ValueFromPipeline = $true)] [Alias('InputObject')] [ValidateScript({ $allowedTypes = "PSGSuite.Chat.Message.Card.Section","PSGSuite.Chat.Message.Card","PSGSuite.Chat.Message.Card.CardAction","PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue" foreach ($item in $_) { if ([string]$($item.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") { $true } else { throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($item.PSTypeNames -join ", ")." } } })] [Object[]] $MessageSegment ) Begin { $widgetObject = @{ Webhook = @{ keyValue = @{} } SDK = (New-Object 'Google.Apis.HangoutsChat.v1.Data.WidgetMarkup' -Property @{ KeyValue = (New-Object 'Google.Apis.HangoutsChat.v1.Data.KeyValue') }) } $widgetStack = @() foreach ($key in $PSBoundParameters.Keys) { switch ($key) { TopLabel { $widgetObject['Webhook']['keyValue']['topLabel'] = $PSBoundParameters[$key] $widgetObject['SDK'].KeyValue.TopLabel = $PSBoundParameters[$key] } Content { $widgetObject['Webhook']['keyValue']['content'] = $PSBoundParameters[$key] $widgetObject['SDK'].KeyValue.Content = $PSBoundParameters[$key] } BottomLabel { $widgetObject['Webhook']['keyValue']['bottomLabel'] = $PSBoundParameters[$key] $widgetObject['SDK'].KeyValue.BottomLabel = $PSBoundParameters[$key] } Icon { $widgetObject['Webhook']['keyValue']['icon'] = $PSBoundParameters[$key] $widgetObject['SDK'].KeyValue.Icon = $PSBoundParameters[$key] } IconUrl { $widgetObject['Webhook']['keyValue']['iconUrl'] = $PSBoundParameters[$key] $widgetObject['SDK'].KeyValue.IconUrl = $PSBoundParameters[$key] } ContentMultiline { $widgetObject['Webhook']['keyValue']['contentMultiline'] = $PSBoundParameters[$key] $widgetObject['SDK'].KeyValue.ContentMultiline = $PSBoundParameters[$key] } OnClick { $widgetObject['Webhook']['keyValue']['onClick'] = $PSBoundParameters[$key]['Webhook'] $widgetObject['SDK'].KeyValue.OnClick = $PSBoundParameters[$key]['SDK'] } Button { $widgetObject['Webhook']['keyValue']['button'] = $PSBoundParameters[$key]['Webhook'] $widgetObject['SDK'].KeyValue.Button = $PSBoundParameters[$key]['SDK'] } } } } Process { foreach ($segment in $MessageSegment) { if ($segment.PSTypeNames[0] -in @("PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue")) { $widgetStack += $segment } else { $segment } } } End { [void]$widgetObject.PSObject.TypeNames.Insert(0,'PSGSuite.Chat.Message.Card.Section.KeyValue') if ($widgetStack) { $widgetStack += $widgetObject $widgetStack } else { $widgetObject } } } Export-ModuleMember -Function 'Add-GSChatKeyValue' function Add-GSChatOnClick { <# .SYNOPSIS Creates a Chat OnClick action to include in a widget .DESCRIPTION Creates a Chat OnClick action to include in a widget .PARAMETER Url The Url to open for an OpenLink action on click .PARAMETER ActionMethodName Apps Script function to invoke when the containing element is clicked/activated. .PARAMETER ActionParameters A hashtable containing key/value pairs of parameters to pass to the ActionMethod .EXAMPLE Send-GSChatMessage -Text "Post job report:" -Cards $cards -Webhook (Get-GSChatWebhook JobReports) Sends a simple Chat message using the JobReports webhook .EXAMPLE Add-GSChatTextParagraph -Text "Guys...","We <b>NEED</b> to <i>stop</i> spending money on <b>crap</b>!" | Add-GSChatKeyValue -TopLabel "Chocolate Budget" -Content '$5.00' -Icon DOLLAR | Add-GSChatKeyValue -TopLabel "Actual Spending" -Content '$5,000,000!' -BottomLabel "WTF" -Icon AIRPLANE | Add-GSChatImage -ImageUrl "https://media.tenor.com/images/f78545a9b520ecf953578b4be220f26d/tenor.gif" -LinkImage | Add-GSChatCardSection -SectionHeader "Dollar bills, y'all" -OutVariable sect1 | Add-GSChatButton -Text "Launch nuke" -OnClick (Add-GSChatOnClick -Url "https://github.com/scrthq/PSGSuite") -Verbose -OutVariable button1 | Add-GSChatButton -Text "Unleash hounds" -OnClick (Add-GSChatOnClick -Url "https://admin.google.com/?hl=en&authuser=0") -Verbose -OutVariable button2 | Add-GSChatCardSection -SectionHeader "What should we do?" -OutVariable sect2 | Add-GSChatCard -HeaderTitle "Makin' moves with" -HeaderSubtitle "DEM GOODIES" -OutVariable card | Add-GSChatTextParagraph -Text "This message sent by <b>PSGSuite</b> via WebHook!" | Add-GSChatCardSection -SectionHeader "Additional Info" -OutVariable sect2 | Send-GSChatMessage -Text "Got that report, boss:" -FallbackText "Mistakes have been made..." -Webhook ReportRoom This example shows the pipeline capabilities of the Chat functions in PSGSuite. Starting from top to bottom: 1. Add a TextParagraph widget 2. Add a KeyValue with an icon 3. Add another KeyValue with a different icon 4. Add an image and create an OnClick event to open the image's URL by using the -LinkImage parameter 5. Add a new section to encapsulate the widgets sent through the pipeline before it 6. Add a TextButton that opens the PSGSuite GitHub repo when clicked 7. Add another TextButton that opens Google Admin Console when clicked 8. Wrap the 2 buttons in a new Section to divide the content 9. Wrap all widgets and sections in the pipeline so far in a Card 10. Add a new TextParagraph as a footer to the message 11. Wrap that TextParagraph in a new section 12. Send the message and include FallbackText that's displayed in the mobile notification. Since the final TextParagraph and Section are not followed by a new Card addition, Send-GSChatMessage will create a new Card just for the remaining segments then send the completed message via Webhook. The Webhook short-name is used to reference the full URL stored in the encrypted Config so it's not displayed in the actual script. .EXAMPLE Get-Service | Select-Object -First 5 | ForEach-Object { Add-GSChatKeyValue -TopLabel $_.DisplayName -Content $_.Status -BottomLabel $_.Name -Icon TICKET } | Add-GSChatCardSection -SectionHeader "Top 5 Services" | Send-GSChatMessage -Text "Service Report:" -FallbackText "Service Report" -Webhook Reports This gets the first 5 Services returned by Get-Service, creates KeyValue widgets for each, wraps it in a section with a header, then sends it to the Reports Webhook #> [CmdletBinding(DefaultParameterSetName = "OpenLink")] Param ( [parameter(Mandatory = $true,Position = 0,ParameterSetName = "OpenLink")] [String] $Url, [parameter(Mandatory = $true,ParameterSetName = "Action")] [String] $ActionMethodName, [parameter(Mandatory = $false,ParameterSetName = "Action")] [Hashtable[]] $ActionParameters ) Begin { $onClickObject = @{ Webhook = @{} SDK = (New-Object 'Google.Apis.HangoutsChat.v1.Data.OnClick') } } Process { switch ($PSCmdlet.ParameterSetName) { OpenLink { $onClickObject['Webhook']['openLink'] = @{ url = $Url } $onClickObject['SDK'].OpenLink = (New-Object 'Google.Apis.HangoutsChat.v1.Data.OpenLink' -Property @{ Url = $Url }) } Action { $onClickObject['Webhook']['action'] = @{ actionMethodName = $ActionMethodName } $onClickObject['SDK'].Action = (New-Object 'Google.Apis.HangoutsChat.v1.Data.FormAction' -Property @{ ActionMethodName = $ActionMethodName }) if ($PSBoundParameters.Keys -contains 'ActionParameters') { $onClickObject['Webhook']['action']['parameters'] = @() $onClickObject['SDK'].Action.Parameters = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.ActionParameter]' foreach ($dict in $ActionParameters) { if ($dict.Keys.Count -eq 2 -and $dict.Keys -contains 'key' -and $dict.Keys -contains 'value') { $onClickObject['Webhook']['action']['parameters'] += ([PSCustomObject]@{ key = "$($dict['key'])" value = "$($dict['value'])" }) $onClickObject['SDK'].Action.Parameters.Add((New-Object 'Google.Apis.HangoutsChat.v1.Data.ActionParameter' -Property @{ Key = $dict['key'] Value = $dict['value'] })) | Out-Null } else { foreach ($key in $dict.Keys) { $onClickObject['Webhook']['action']['parameters'] += ([PSCustomObject]@{ key = "$key" value = "$($dict[$key])" }) $onClickObject['SDK'].Action.Parameters.Add((New-Object 'Google.Apis.HangoutsChat.v1.Data.ActionParameter' -Property @{ Key = $key Value = $dict[$key] })) | Out-Null } } } } } } } End { [void]$onClickObject.PSObject.TypeNames.Insert(0,'PSGSuite.Chat.Message.Card.OnClick') $onClickObject } } Export-ModuleMember -Function 'Add-GSChatOnClick' function Add-GSChatTextParagraph { <# .SYNOPSIS Creates a Chat TextParagraph widget to include in a section .DESCRIPTION Creates a Chat TextParagraph widget to include in a section .PARAMETER Text The text for the textParagraph .PARAMETER MessageSegment Any Chat message segment objects created with functions named `Add-GSChat*` passed through the pipeline or added directly to this parameter as values. .EXAMPLE Send-GSChatMessage -Text "Post job report:" -Cards $cards -Webhook (Get-GSChatWebhook JobReports) Sends a simple Chat message using the JobReports webhook .EXAMPLE Add-GSChatTextParagraph -Text "Guys...","We <b>NEED</b> to <i>stop</i> spending money on <b>crap</b>!" | Add-GSChatKeyValue -TopLabel "Chocolate Budget" -Content '$5.00' -Icon DOLLAR | Add-GSChatKeyValue -TopLabel "Actual Spending" -Content '$5,000,000!' -BottomLabel "WTF" -Icon AIRPLANE | Add-GSChatImage -ImageUrl "https://media.tenor.com/images/f78545a9b520ecf953578b4be220f26d/tenor.gif" -LinkImage | Add-GSChatCardSection -SectionHeader "Dollar bills, y'all" -OutVariable sect1 | Add-GSChatButton -Text "Launch nuke" -OnClick (Add-GSChatOnClick -Url "https://github.com/scrthq/PSGSuite") -Verbose -OutVariable button1 | Add-GSChatButton -Text "Unleash hounds" -OnClick (Add-GSChatOnClick -Url "https://admin.google.com/?hl=en&authuser=0") -Verbose -OutVariable button2 | Add-GSChatCardSection -SectionHeader "What should we do?" -OutVariable sect2 | Add-GSChatCard -HeaderTitle "Makin' moves with" -HeaderSubtitle "DEM GOODIES" -OutVariable card | Add-GSChatTextParagraph -Text "This message sent by <b>PSGSuite</b> via WebHook!" | Add-GSChatCardSection -SectionHeader "Additional Info" -OutVariable sect2 | Send-GSChatMessage -Text "Got that report, boss:" -FallbackText "Mistakes have been made..." -Webhook ReportRoom This example shows the pipeline capabilities of the Chat functions in PSGSuite. Starting from top to bottom: 1. Add a TextParagraph widget 2. Add a KeyValue with an icon 3. Add another KeyValue with a different icon 4. Add an image and create an OnClick event to open the image's URL by using the -LinkImage parameter 5. Add a new section to encapsulate the widgets sent through the pipeline before it 6. Add a TextButton that opens the PSGSuite GitHub repo when clicked 7. Add another TextButton that opens Google Admin Console when clicked 8. Wrap the 2 buttons in a new Section to divide the content 9. Wrap all widgets and sections in the pipeline so far in a Card 10. Add a new TextParagraph as a footer to the message 11. Wrap that TextParagraph in a new section 12. Send the message and include FallbackText that's displayed in the mobile notification. Since the final TextParagraph and Section are not followed by a new Card addition, Send-GSChatMessage will create a new Card just for the remaining segments then send the completed message via Webhook. The Webhook short-name is used to reference the full URL stored in the encrypted Config so it's not displayed in the actual script. .EXAMPLE Get-Service | Select-Object -First 5 | ForEach-Object { Add-GSChatKeyValue -TopLabel $_.DisplayName -Content $_.Status -BottomLabel $_.Name -Icon TICKET } | Add-GSChatCardSection -SectionHeader "Top 5 Services" | Send-GSChatMessage -Text "Service Report:" -FallbackText "Service Report" -Webhook Reports This gets the first 5 Services returned by Get-Service, creates KeyValue widgets for each, wraps it in a section with a header, then sends it to the Reports Webhook #> Param ( [parameter(Mandatory = $true,Position = 0)] [String[]] $Text, [parameter(Mandatory = $false,ValueFromPipeline = $true)] [Alias('InputObject')] [ValidateScript({ $allowedTypes = "PSGSuite.Chat.Message.Card.Section","PSGSuite.Chat.Message.Card","PSGSuite.Chat.Message.Card.CardAction","PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue" foreach ($item in $_) { if ([string]$($item.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") { $true } else { throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($item.PSTypeNames -join ", ")." } } })] [Object[]] $MessageSegment ) Begin { $widgetObject = @{ Webhook = @{ textParagraph = @{ text = ($Text -join "`n") } } SDK = (New-Object 'Google.Apis.HangoutsChat.v1.Data.WidgetMarkup' -Property @{ TextParagraph = (New-Object 'Google.Apis.HangoutsChat.v1.Data.TextParagraph' -Property @{ Text = ($Text -join "`n") }) }) } $widgetStack = @() } Process { foreach ($segment in $MessageSegment) { if ($segment.PSTypeNames[0] -in @("PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue")) { $widgetStack += $segment } else { $segment } } } End { [void]$widgetObject.PSObject.TypeNames.Insert(0,'PSGSuite.Chat.Message.Card.Section.TextParagraph') if ($widgetStack) { $widgetStack += $widgetObject $widgetStack } else { $widgetObject } } } Export-ModuleMember -Function 'Add-GSChatTextParagraph' function Add-GSCustomerPostalAddress { <# .SYNOPSIS Builds a PostalAddress object to use when creating or updating a Customer .DESCRIPTION Builds a PostalAddress object to use when creating or updating a Customer .PARAMETER AddressLine1 A customer's physical address. The address can be composed of one to three lines. .PARAMETER AddressLine2 Address line 2 of the address. .PARAMETER AddressLine3 Address line 3 of the address. .PARAMETER ContactName The customer contact's name. .PARAMETER CountryCode The country code. Uses the ISO 3166-1 standard: http://www.iso.org/iso/iso-3166-1_decoding_table .PARAMETER Locality Name of the locality. An example of a locality value is the city of San Francisco. .PARAMETER OrganizationName The company or company division name. .PARAMETER PostalCode The postal code. A postalCode example is a postal zip code such as 10009. This is in accordance with - http://portablecontacts.net/draft-spec.html#address_element. .PARAMETER Region Name of the region. An example of a region value is NY for the state of New York. .PARAMETER InputObject Used for pipeline input of an existing UserAddress object to strip the extra attributes and prevent errors .EXAMPLE Add-GSCustomerPostalAddress -AddressLine1 '123 Front St' -AddressLine2 'Los Angeles, CA 90210' -ContactName 'Jim' #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.CustomerPostalAddress')] [CmdletBinding(DefaultParameterSetName = "InputObject")] Param ( [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $AddressLine1, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $AddressLine2, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $AddressLine3, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $ContactName, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $CountryCode, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [Alias('Town', 'City')] [String] $Locality, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $OrganizationName, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $PostalCode, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [Alias('State', 'Province')] [String] $Region, [Parameter(Mandatory = $false, ValueFromPipeline = $true, ParameterSetName = "InputObject")] [Google.Apis.Admin.Directory.directory_v1.Data.CustomerPostalAddress] $InputObject ) Begin { $propsToWatch = @( 'AddressLine1' 'AddressLine2' 'AddressLine3' 'ContactName' 'CountryCode' 'Locality' 'OrganizationName' 'PostalCode' 'Region' ) } Process { try { switch ($PSCmdlet.ParameterSetName) { Fields { $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.CustomerPostalAddress' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$obj.PSObject.Properties.Name -contains $_}) { $obj.$prop = $PSBoundParameters[$prop] } $obj } InputObject { $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.CustomerPostalAddress' foreach ($prop in $InputObject.PSObject.Properties.Name | Where-Object {$obj.PSObject.Properties.Name -contains $_ -and $propsToWatch -contains $_}) { $obj.$prop = $InputObject.$prop } $obj } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Add-GSCustomerPostalAddress' function Add-GSEventAttendee { <# .SYNOPSIS Adds an event attendee to a calendar event .DESCRIPTION Adds an event attendee to a calendar event .PARAMETER Email The email address of the attendee .PARAMETER AdditionalGuests How many additional guests, if any .PARAMETER Comment Attendee comment .PARAMETER DisplayName The attendee's name, if available .PARAMETER Optional Whether this is an optional attendee .PARAMETER Organizer Whether the attendee is the organizer of the event .PARAMETER Resource Whether the attendee is a resource .PARAMETER ResponseStatus The attendee's response status. Possible values are: * "NeedsAction": The attendee has not responded to the invitation. * "Declined": The attendee has declined the invitation. * "Tentative": The attendee has tentatively accepted the invitation. * "Accepted": The attendee has accepted the invitation .PARAMETER InputObject Used for pipeline input of an existing UserAddress object to strip the extra attributes and prevent errors .EXAMPLE Add-GSEventAttendee -Email 'joe@domain.com' #> [OutputType('Google.Apis.Calendar.v3.Data.EventAttendee')] [CmdletBinding(DefaultParameterSetName = "InputObject")] Param ( [Parameter(Mandatory = $true,ParameterSetName = "Fields")] [String] $Email, [Parameter(Mandatory = $false,ParameterSetName = "Fields")] [Int] $AdditionalGuests, [Parameter(Mandatory = $false,ParameterSetName = "Fields")] [String] $Comment, [Parameter(Mandatory = $false,ParameterSetName = "Fields")] [String] $DisplayName, [Parameter(Mandatory = $false,ParameterSetName = "Fields")] [Switch] $Optional, [Parameter(Mandatory = $false,ParameterSetName = "Fields")] [Switch] $Organizer, [Parameter(Mandatory = $false,ParameterSetName = "Fields")] [Switch] $Resource, [Parameter(Mandatory = $false,ParameterSetName = "Fields")] [ValidateSet('NeedsAction','Declined','Tentative','Accepted')] [String] $ResponseStatus, [Parameter(Mandatory = $false,ValueFromPipeline = $true,ParameterSetName = "InputObject")] [Google.Apis.Calendar.v3.Data.EventAttendee[]] $InputObject ) Begin { $propsToWatch = @( 'AdditionalGuests' 'Comment' 'DisplayName' 'Email' 'Optional' 'Organizer' 'Resource' 'ResponseStatus' ) } Process { try { switch ($PSCmdlet.ParameterSetName) { Fields { Write-Verbose "Adding event attendee '$Email'" $obj = New-Object 'Google.Apis.Calendar.v3.Data.EventAttendee' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$obj.PSObject.Properties.Name -contains $_}) { $obj.$prop = $PSBoundParameters[$prop] } $obj } InputObject { foreach ($iObj in $InputObject) { $obj = New-Object 'Google.Apis.Calendar.v3.Data.EventAttendee' foreach ($prop in $iObj.PSObject.Properties.Name | Where-Object {$obj.PSObject.Properties.Name -contains $_ -and $propsToWatch -contains $_}) { $obj.$prop = $iObj.$prop } $obj } } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Add-GSEventAttendee' function Add-GSGmailSmtpMsa { <# .SYNOPSIS Builds a SmtpMsa object to use when creating or updating SmtpMsa settings withing the Gmail SendAs settings. .DESCRIPTION Builds a SmtpMsa object to use when creating or updating SmtpMsa settings withing the Gmail SendAs settings. .PARAMETER HostName The hostname of the SMTP service. .PARAMETER Port The port of the SMTP service. .PARAMETER SecurityMode The protocol that will be used to secure communication with the SMTP service. Acceptable values are: * "none" * "securityModeUnspecified" * "ssl" * "starttls" .PARAMETER Username The username that will be used for authentication with the SMTP service. This is a write-only field that can be specified in requests to create or update SendAs settings; it is never populated in responses. .PARAMETER Password The password that will be used for authentication with the SMTP service. This is a write-only field that can be specified in requests to create or update SendAs settings; it is never populated in responses. .PARAMETER TreatAsAlias Whether Gmail should treat this address as an alias for the user's primary email address. This setting only applies to custom "from" aliases. .PARAMETER InputObject Used for pipeline input of an existing UserAddress object to strip the extra attributes and prevent errors .EXAMPLE $smtpMsa = Add-GSGmailSmtpMsa -Host 10.0.30.18 -Port 3770 -SecurityMode none -Username mailadmin -Password $(ConvertTo-SecureString $password -AsPlainText -Force) Update-GSGmailSendAsSettings -SendAsEmail joseph.wiggum@business.com -User joe@domain.com -Signature "<div>Thank you for your time,</br>Joseph Wiggum</div>" -SmtpMsa $smtpMsa Updates Joe's SendAs settings for his work SendAs alias, including signature and SmtpMsa settings. #> [OutputType('Google.Apis.Gmail.v1.Data.SmtpMsa')] [CmdletBinding(DefaultParameterSetName = "InputObject")] Param ( [Parameter(Mandatory = $true, ParameterSetName = "Fields")] [Alias('Host')] [string] $HostName, [Parameter(Mandatory = $true, ParameterSetName = "Fields")] [int] $Port, [Parameter(Mandatory = $true, ParameterSetName = "Fields")] [ValidateSet('none','securityModeUnspecified','ssl','starttls')] [String] $SecurityMode, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $Username, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [SecureString] $Password, [Parameter(Mandatory = $false, ValueFromPipeline = $true, ParameterSetName = "InputObject")] [Google.Apis.Gmail.v1.Data.SmtpMsa[]] $InputObject ) Begin { $propsToWatch = @( 'HostName' 'Port' 'SecurityMode' 'Username' 'Password' 'TreatAsAlias' ) } Process { try { switch ($PSCmdlet.ParameterSetName) { Fields { $obj = New-Object 'Google.Apis.Gmail.v1.Data.SmtpMsa' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$obj.PSObject.Properties.Name -contains $_}) { $obj.$prop = $PSBoundParameters[$prop] } $obj } InputObject { foreach ($iObj in $InputObject) { $obj = New-Object 'Google.Apis.Gmail.v1.Data.SmtpMsa' foreach ($prop in $iObj.PSObject.Properties.Name | Where-Object {$obj.PSObject.Properties.Name -contains $_ -and $propsToWatch -contains $_}) { $obj.$prop = $iObj.$prop } $obj } } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Add-GSGmailSmtpMsa' function Add-GSUserAddress { <# .SYNOPSIS Builds a UserAddress object to use when creating or updating a User .DESCRIPTION Builds a UserAddress object to use when creating or updating a User .PARAMETER Country Country .PARAMETER CountryCode The country code. Uses the ISO 3166-1 standard: http://www.iso.org/iso/iso-3166-1_decoding_table .PARAMETER CustomType If the address type is custom, this property contains the custom value .PARAMETER ExtendedAddress For extended addresses, such as an address that includes a sub-region .PARAMETER Formatted A full and unstructured postal address. This is not synced with the structured address fields .PARAMETER Locality The town or city of the address .PARAMETER PoBox The post office box, if present .PARAMETER PostalCode The ZIP or postal code, if applicable .PARAMETER Primary If this is the user's primary address. The addresses list may contain only one primary address .PARAMETER Region The abbreviated province or state .PARAMETER SourceIsStructured Indicates if the user-supplied address was formatted. Formatted addresses are not currently supported .PARAMETER StreetAddress The street address, such as 1600 Amphitheatre Parkway. Whitespace within the string is ignored; however, newlines are significant .PARAMETER Type The address type. Acceptable values are: * "custom" * "home" * "other" * "work" .PARAMETER InputObject Used for pipeline input of an existing UserAddress object to strip the extra attributes and prevent errors .EXAMPLE $address = Add-GSUserAddress -Country USA -Locality Dallas -PostalCode 75000 Region TX -StreetAddress '123 South St' -Type Work -Primary $phone = Add-GSUserPhone -Type Work -Value "(800) 873-0923" -Primary $extId = Add-GSUserExternalId -Type Login_Id -Value jsmith2 $email = Add-GSUserEmail -Type work -Address jsmith@contoso.com New-GSUser -PrimaryEmail john.smith@domain.com -GivenName John -FamilyName Smith -Password (ConvertTo-SecureString -String 'Password123' -AsPlainText -Force) -ChangePasswordAtNextLogin -OrgUnitPath "/Users/New Hires" -IncludeInGlobalAddressList -Addresses $address -Phones $phone -ExternalIds $extId -Emails $email Creates a user named John Smith and adds their work address, work phone, login_id and alternate non gsuite work email to the user object. #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.UserAddress')] [CmdletBinding(DefaultParameterSetName = "InputObject")] Param ( [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $Country, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $CountryCode, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $CustomType, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $ExtendedAddress, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $Formatted, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [Alias('Town', 'City')] [String] $Locality, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $PoBox, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $PostalCode, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [Switch] $Primary, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [Alias('State', 'Province')] [String] $Region, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [Switch] $SourceIsStructured, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $StreetAddress, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [ValidateSet('custom', 'home', 'other', 'work')] [String] $Type, [Parameter(Mandatory = $false, ValueFromPipeline = $true, ParameterSetName = "InputObject")] [Google.Apis.Admin.Directory.directory_v1.Data.UserAddress[]] $InputObject ) Begin { $propsToWatch = @( 'Country' 'CountryCode' 'CustomType' 'ExtendedAddress' 'Formatted' 'Locality' 'PoBox' 'PostalCode' 'Primary' 'Region' 'SourceIsStructured' 'StreetAddress' 'Type' ) } Process { try { switch ($PSCmdlet.ParameterSetName) { Fields { $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserAddress' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$obj.PSObject.Properties.Name -contains $_}) { if ($prop -eq 'Type') { $obj.$prop = $PSBoundParameters[$prop].ToLower() } else { $obj.$prop = $PSBoundParameters[$prop] } } $obj } InputObject { foreach ($iObj in $InputObject) { $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserAddress' foreach ($prop in $iObj.PSObject.Properties.Name | Where-Object {$obj.PSObject.Properties.Name -contains $_ -and $propsToWatch -contains $_}) { $obj.$prop = $iObj.$prop } $obj } } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Add-GSUserAddress' function Add-GSUserEmail { <# .SYNOPSIS Builds a Email object to use when creating or updating a User .DESCRIPTION Builds a Email object to use when creating or updating a User .PARAMETER Address The user's email address. Also serves as the email ID. This value can be the user's primary email address or an alias. .PARAMETER CustomType If the value of type is custom, this property contains the custom type. .PARAMETER Primary Indicates if this is the user's primary email. Only one entry can be marked as primary. .PARAMETER Type The type of the email account. Acceptable values are: * "custom" * "home" * "other" * "work" .PARAMETER InputObject Used for pipeline input of an existing Email object to strip the extra attributes and prevent errors .EXAMPLE $address = Add-GSUserAddress -Country USA -Locality Dallas -PostalCode 75000 Region TX -StreetAddress '123 South St' -Type Work -Primary $phone = Add-GSUserPhone -Type Work -Value "(800) 873-0923" -Primary $extId = Add-GSUserExternalId -Type Login_Id -Value jsmith2 $email = Add-GSUserEmail -Type work -Address jsmith@contoso.com New-GSUser -PrimaryEmail john.smith@domain.com -GivenName John -FamilyName Smith -Password (ConvertTo-SecureString -String 'Password123' -AsPlainText -Force) -ChangePasswordAtNextLogin -OrgUnitPath "/Users/New Hires" -IncludeInGlobalAddressList -Addresses $address -Phones $phone -ExternalIds $extId -Emails $email Creates a user named John Smith and adds their work address, work phone, login_id and alternate non gsuite work email to the user object. #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.UserEmail')] [CmdletBinding()] Param ( [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $Address, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $CustomType, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [Switch] $Primary, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [ValidateSet('custom', 'home', 'other', 'work')] [String] $Type, [Parameter(Mandatory = $false, ValueFromPipeline = $true, ParameterSetName = "InputObject")] [Google.Apis.Admin.Directory.directory_v1.Data.UserEmail[]] $InputObject ) Begin { $propsToWatch = @( 'Address' 'CustomType' 'Type' ) } Process { try { switch ($PSCmdlet.ParameterSetName) { Fields { $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserEmail' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$obj.PSObject.Properties.Name -contains $_}) { if ($prop -eq 'Type') { $obj.$prop = $PSBoundParameters[$prop].ToLower() } else { $obj.$prop = $PSBoundParameters[$prop] } } $obj } InputObject { foreach ($iObj in $InputObject) { $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserEmail' foreach ($prop in $iObj.PSObject.Properties.Name | Where-Object {$obj.PSObject.Properties.Name -contains $_ -and $propsToWatch -contains $_}) { $obj.$prop = $iObj.$prop } $obj } } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Add-GSUserEmail' function Add-GSUserExternalId { <# .SYNOPSIS Builds a UserExternalId object to use when creating or updating a User .DESCRIPTION Builds a UserExternalId object to use when creating or updating a User .PARAMETER CustomType If the external ID type is custom, this property holds the custom type .PARAMETER Type The type of the ID. Acceptable values are: * "account" * "custom" * "customer" * "login_id" * "network" * "organization": For example, Employee ID. .PARAMETER Value The value of the ID .PARAMETER InputObject Used for pipeline input of an existing UserExternalId object to strip the extra attributes and prevent errors .EXAMPLE $address = Add-GSUserAddress -Country USA -Locality Dallas -PostalCode 75000 Region TX -StreetAddress '123 South St' -Type Work -Primary $phone = Add-GSUserPhone -Type Work -Value "(800) 873-0923" -Primary $extId = Add-GSUserExternalId -Type Login_Id -Value jsmith2 $email = Add-GSUserEmail -Type work -Address jsmith@contoso.com New-GSUser -PrimaryEmail john.smith@domain.com -GivenName John -FamilyName Smith -Password (ConvertTo-SecureString -String 'Password123' -AsPlainText -Force) -ChangePasswordAtNextLogin -OrgUnitPath "/Users/New Hires" -IncludeInGlobalAddressList -Addresses $address -Phones $phone -ExternalIds $extId -Emails $email Creates a user named John Smith and adds their work address, work phone, login_id and alternate non gsuite work email to the user object. #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.UserExternalId')] [CmdletBinding(DefaultParameterSetName = "InputObject")] Param ( [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $CustomType, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [ValidateSet('account', 'custom', 'customer', 'login_id', 'network', 'organization')] [String] $Type, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [Alias('ExternalId')] [String] $Value, [Parameter(Mandatory = $false, ValueFromPipeline = $true, ParameterSetName = "InputObject")] [Google.Apis.Admin.Directory.directory_v1.Data.UserExternalId[]] $InputObject ) Begin { $propsToWatch = @( 'CustomType' 'Type' 'Value' ) } Process { try { switch ($PSCmdlet.ParameterSetName) { Fields { $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserExternalId' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$obj.PSObject.Properties.Name -contains $_}) { if ($prop -eq 'Type') { $obj.$prop = $PSBoundParameters[$prop].ToLower() } else { $obj.$prop = $PSBoundParameters[$prop] } } $obj } InputObject { foreach ($iObj in $InputObject) { $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserExternalId' foreach ($prop in $iObj.PSObject.Properties.Name | Where-Object {$obj.PSObject.Properties.Name -contains $_ -and $propsToWatch -contains $_}) { $obj.$prop = $iObj.$prop } $obj } } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Add-GSUserExternalId' function Add-GSUserIm { <# .SYNOPSIS Builds an IM object to use when creating or updating a User .DESCRIPTION Builds an IM object to use when creating or updating a User .PARAMETER CustomProtocol If the protocol value is custom_protocol, this property holds the custom protocol's string. .PARAMETER CustomType If the value of type is custom, this property contains the custom type. .PARAMETER Im The user's IM network ID. .PARAMETER Primary If this is the user's primary IM. Only one entry in the IM list can have a value of true. .PARAMETER Protocol An IM protocol identifies the IM network. The value can be a custom network or the standard network. Acceptable values are: * "aim": AOL Instant Messenger protocol * "custom_protocol": A custom IM network protocol * "gtalk": Google Talk protocol * "icq": ICQ protocol * "jabber": Jabber protocol * "msn": MSN Messenger protocol * "net_meeting": Net Meeting protocol * "qq": QQ protocol * "skype": Skype protocol * "yahoo": Yahoo Messenger protocol "aim","custom_protocol","gtalk","icq","jabber","msn","net_meeting","qq","skype","yahoo" .PARAMETER Type The type of the IM account. Acceptable values are: * "custom" * "home" * "other" * "work" .PARAMETER InputObject Used for pipeline input of an existing IM object to strip the extra attributes and prevent errors .EXAMPLE $address = Add-GSUserAddress -Country USA -Locality Dallas -PostalCode 75000 Region TX -StreetAddress '123 South St' -Type Work -Primary $phone = Add-GSUserPhone -Type Work -Value "(800) 873-0923" -Primary $extId = Add-GSUserExternalId -Type Login_Id -Value jsmith2 $email = Add-GSUserEmail -Type work -Address jsmith@contoso.com $im = Add-GSUserIm -Type work -Protocol custom_protocol -CustomProtocol spark -Im jsmithertons100 New-GSUser -PrimaryEmail john.smith@domain.com -GivenName John -FamilyName Smith -Password (ConvertTo-SecureString -String 'Password123' -AsPlainText -Force) -ChangePasswordAtNextLogin -OrgUnitPath "/Users/New Hires" -IncludeInGlobalAddressList -Addresses $address -Phones $phone -ExternalIds $extId -Emails $email -Ims $im Creates a user named John Smith and adds their work address, work phone, IM, login_id and alternate non gsuite work email to the user object. #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.UserIm')] [CmdletBinding()] Param ( [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $CustomProtocol, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $CustomType, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $Im, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [Switch] $Primary, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [ValidateSet("aim","custom_protocol","gtalk","icq","jabber","msn","net_meeting","qq","skype","yahoo")] [String] $Protocol, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [ValidateSet('custom', 'home', 'other', 'work')] [String] $Type, [Parameter(Mandatory = $false, ValueFromPipeline = $true, ParameterSetName = "InputObject")] [Google.Apis.Admin.Directory.directory_v1.Data.UserIm[]] $InputObject ) Begin { $propsToWatch = @( 'CustomProtocol' 'CustomType' 'Im' 'Primary' 'Protocol' 'Type' ) } Process { try { switch ($PSCmdlet.ParameterSetName) { Fields { $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserIm' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$obj.PSObject.Properties.Name -contains $_}) { if ($prop -in @('Type','Protocol')) { $obj.$prop = $PSBoundParameters[$prop].ToLower() } else { $obj.$prop = $PSBoundParameters[$prop] } } $obj } InputObject { foreach ($iObj in $InputObject) { $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserIm' foreach ($prop in $iObj.PSObject.Properties.Name | Where-Object {$obj.PSObject.Properties.Name -contains $_ -and $propsToWatch -contains $_}) { $obj.$prop = $iObj.$prop } $obj } } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Add-GSUserIm' function Add-GSUserLocation { <# .SYNOPSIS Builds a Location object to use when creating or updating a User .DESCRIPTION Builds a Location object to use when creating or updating a User .PARAMETER Area Textual location. This is most useful for display purposes to concisely describe the location. For example, "Mountain View, CA", "Near Seattle", "US-NYC-9TH 9A209A" .PARAMETER BuildingId Building Identifier. .PARAMETER CustomType Custom Type. .PARAMETER DeskCode Most specific textual code of individual desk location. .PARAMETER FloorName Floor name/number. .PARAMETER FloorSection Floor section. More specific location within the floor. For example, if a floor is divided into sections "A", "B", and "C", this field would identify one of those values. .PARAMETER Type Each entry can have a type which indicates standard types of that entry. For example location could be of types default and desk. In addition to standard type, an entry can have a custom type and can give it any name. Such types should have "custom" as type and also have a customType value. Acceptable values are: * "custom" * "default" * "desk" .PARAMETER InputObject Used for pipeline input of an existing Location object to strip the extra attributes and prevent errors .EXAMPLE Add-GSUserLocation -Area "Bellevue, WA" -BuildingId '30' -CustomType "LemonadeStand" -Type custom Adds a custom user location. #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.UserLocation')] [CmdletBinding()] Param ( [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $Area, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $BuildingId, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $CustomType, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $DeskCode, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $FloorName, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $FloorSection, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [ValidateSet('custom', 'default', 'desk')] [String] $Type, [Parameter(Mandatory = $false, ValueFromPipeline = $true, ParameterSetName = "InputObject")] [Google.Apis.Admin.Directory.directory_v1.Data.UserLocation[]] $InputObject ) Begin { $propsToWatch = @( 'Area' 'BuildingId' 'CustomType' 'DeskCode' 'FloorName' 'FloorSection' 'Type' ) } Process { try { switch ($PSCmdlet.ParameterSetName) { Fields { $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserLocation' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$obj.PSObject.Properties.Name -contains $_}) { if ($prop -eq 'Type') { $obj.$prop = $PSBoundParameters[$prop].ToLower() } else { $obj.$prop = $PSBoundParameters[$prop] } } $obj } InputObject { foreach ($iObj in $InputObject) { $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserLocation' foreach ($prop in $iObj.PSObject.Properties.Name | Where-Object {$obj.PSObject.Properties.Name -contains $_ -and $propsToWatch -contains $_}) { $obj.$prop = $iObj.$prop } $obj } } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Add-GSUserLocation' function Add-GSUserOrganization { <# .SYNOPSIS Builds a Organization object to use when creating or updating a User .DESCRIPTION Builds a Organization object to use when creating or updating a User .PARAMETER CostCenter The cost center of the users department .PARAMETER CustomType If the external ID type is custom, this property holds the custom type .PARAMETER Department Department within the organization .PARAMETER Description Description of the organization .PARAMETER Domain The domain to which the organization belongs to .PARAMETER FullTimeEquivalent The full-time equivalent percent within the organization (100000 = 100%). .PARAMETER Location Location of the organization. This need not be fully qualified address. .PARAMETER Name Name of the organization .PARAMETER Primary If it is the user's primary organization .PARAMETER Symbol Symbol of the organization .PARAMETER Title Title (designation) of the user in the organization .PARAMETER Type The type of the organization. If using a CustomType .PARAMETER Value The value of the ID .PARAMETER InputObject Used for pipeline input of an existing UserExternalId object to strip the extra attributes and prevent errors .EXAMPLE $address = Add-GSUserAddress -Country USA -Locality Dallas -PostalCode 75000 Region TX -StreetAddress '123 South St' -Type Work -Primary $phone = Add-GSUserPhone -Type Work -Value "(800) 873-0923" -Primary $extId = Add-GSUserExternalId -Type Login_Id -Value jsmith2 $email = Add-GSUserEmail -Type work -Address jsmith@contoso.com New-GSUser -PrimaryEmail john.smith@domain.com -GivenName John -FamilyName Smith -Password (ConvertTo-SecureString -String 'Password123' -AsPlainText -Force) -ChangePasswordAtNextLogin -OrgUnitPath "/Users/New Hires" -IncludeInGlobalAddressList -Addresses $address -Phones $phone -ExternalIds $extId -Emails $email Creates a user named John Smith and adds their work address, work phone, login_id and alternate non gsuite work email to the user object. #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.UserOrganization')] [CmdletBinding(DefaultParameterSetName = "InputObject")] Param ( [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $CostCenter, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $CustomType, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $Department, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $Description, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $Domain, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [Int] $FullTimeEquivalent, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $Location, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $Name, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [Switch] $Primary, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $Symbol, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $Title, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $Type, [Parameter(Mandatory = $false, ValueFromPipeline = $true, ParameterSetName = "InputObject")] [Google.Apis.Admin.Directory.directory_v1.Data.UserOrganization[]] $InputObject ) Begin { $propsToWatch = @( 'CostCenter' 'CustomType' 'Department' 'Description' 'Domain' 'FullTimeEquivalent' 'Location' 'Name' 'Primary' 'Symbol' 'Title' 'Type' ) if ($PSBoundParameters.Keys -contains 'CustomType') { $PSBoundParameters['Type'] = 'CUSTOM' } } Process { try { switch ($PSCmdlet.ParameterSetName) { Fields { $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserOrganization' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$obj.PSObject.Properties.Name -contains $_}) { $obj.$prop = $PSBoundParameters[$prop] } $obj } InputObject { foreach ($iObj in $InputObject) { $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserOrganization' foreach ($prop in $iObj.PSObject.Properties.Name | Where-Object {$obj.PSObject.Properties.Name -contains $_ -and $propsToWatch -contains $_}) { $obj.$prop = $iObj.$prop } $obj } } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Add-GSUserOrganization' function Add-GSUserPhone { <# .SYNOPSIS Builds a UserPhone object to use when creating or updating a User .DESCRIPTION Builds a UserPhone object to use when creating or updating a User .PARAMETER CustomType If the value of type is custom, this property contains the custom type .PARAMETER Primary Indicates if this is the user's primary phone number. A user may only have one primary phone number .PARAMETER Type The type of phone number. Acceptable values are: * "assistant" * "callback" * "car" * "company_main" * "custom" * "grand_central" * "home" * "home_fax" * "isdn" * "main" * "mobile" * "other" * "other_fax" * "pager" * "radio" * "telex" * "tty_tdd" * "work" * "work_fax" * "work_mobile" * "work_pager" .PARAMETER Value A human-readable phone number. It may be in any telephone number format .PARAMETER InputObject Used for pipeline input of an existing UserPhone object to strip the extra attributes and prevent errors .EXAMPLE $address = Add-GSUserAddress -Country USA -Locality Dallas -PostalCode 75000 Region TX -StreetAddress '123 South St' -Type Work -Primary $phone = Add-GSUserPhone -Type Work -Value "(800) 873-0923" -Primary $extId = Add-GSUserExternalId -Type Login_Id -Value jsmith2 $email = Add-GSUserEmail -Type work -Address jsmith@contoso.com New-GSUser -PrimaryEmail john.smith@domain.com -GivenName John -FamilyName Smith -Password (ConvertTo-SecureString -String 'Password123' -AsPlainText -Force) -ChangePasswordAtNextLogin -OrgUnitPath "/Users/New Hires" -IncludeInGlobalAddressList -Addresses $address -Phones $phone -ExternalIds $extId -Emails $email Creates a user named John Smith and adds their work address, work phone, login_id and alternate non gsuite work email to the user object. #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.UserAddress')] [CmdletBinding()] Param ( [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $CustomType, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [Switch] $Primary, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [ValidateSet('assistant', 'callback', 'car', 'company_main', 'custom', 'grand_central', 'home', 'home_fax', 'isdn', 'main', 'mobile', 'other', 'other_fax', 'pager', 'radio', 'telex', 'tty_tdd', 'work', 'work_fax', 'work_mobile', 'work_pager')] [String] $Type, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [Alias('Phone')] [String] $Value, [Parameter(Mandatory = $false, ValueFromPipeline = $true, ParameterSetName = "InputObject")] [Google.Apis.Admin.Directory.directory_v1.Data.UserAddress[]] $InputObject ) Begin { $propsToWatch = @( 'CustomType' 'Type' 'Value' ) } Process { try { switch ($PSCmdlet.ParameterSetName) { Fields { $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserPhone' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$obj.PSObject.Properties.Name -contains $_}) { if ($prop -eq 'Type') { $obj.$prop = $PSBoundParameters[$prop].ToLower() } else { $obj.$prop = $PSBoundParameters[$prop] } } $obj } InputObject { foreach ($iObj in $InputObject) { $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserPhone' foreach ($prop in $iObj.PSObject.Properties.Name | Where-Object {$obj.PSObject.Properties.Name -contains $_ -and $propsToWatch -contains $_}) { $obj.$prop = $iObj.$prop } $obj } } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Add-GSUserPhone' function Add-GSUserRelation { <# .SYNOPSIS Builds a Relation object to use when creating or updating a User .DESCRIPTION Builds a Relation object to use when creating or updating a User .PARAMETER Type The type of relation. Acceptable values are: * "admin_assistant" * "assistant" * "brother" * "child" * "custom" * "domestic_partner" * "dotted_line_manager" * "exec_assistant" * "father" * "friend" * "manager" * "mother" * "parent" * "partner" * "referred_by" * "relative" * "sister" * "spouse" .PARAMETER Value The name of the person the user is related to. .PARAMETER CustomType If the value of `Type` is `custom`, this property contains the custom type. .PARAMETER InputObject Used for pipeline input of an existing UserExternalId object to strip the extra attributes and prevent errors .EXAMPLE $address = Add-GSUserAddress -Country USA -Locality Dallas -PostalCode 75000 Region TX -StreetAddress '123 South St' -Type Work -Primary $phone = Add-GSUserPhone -Type Work -Value "(800) 873-0923" -Primary $extId = Add-GSUserExternalId -Type Login_Id -Value jsmith2 $email = Add-GSUserEmail -Type work -Address jsmith@contoso.com New-GSUser -PrimaryEmail john.smith@domain.com -GivenName John -FamilyName Smith -Password (ConvertTo-SecureString -String 'Password123' -AsPlainText -Force) -ChangePasswordAtNextLogin -OrgUnitPath "/Users/New Hires" -IncludeInGlobalAddressList -Addresses $address -Phones $phone -ExternalIds $extId -Emails $email Creates a user named John Smith and adds their work address, work phone, login_id and alternate non gsuite work email to the user object. #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.UserRelation')] [CmdletBinding(DefaultParameterSetName = "InputObject")] Param ( [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [ValidateSet("admin_assistant","assistant","brother","child","custom","domestic_partner","dotted_line_manager","exec_assistant","father","friend","manager","mother","parent","partner","referred_by","relative","sister","spouse")] [String] $Type, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $Value, [Parameter(Mandatory = $false, ParameterSetName = "Fields")] [String] $CustomType, [Parameter(Mandatory = $false, ValueFromPipeline = $true, ParameterSetName = "InputObject")] [Google.Apis.Admin.Directory.directory_v1.Data.UserRelation[]] $InputObject ) Begin { $propsToWatch = @( 'CustomType' 'Type', 'Value' ) if ($PSBoundParameters.Keys -contains 'CustomType') { $PSBoundParameters['Type'] = 'custom' } $PSBoundParameters['Type'] = $PSBoundParameters['Type'].ToString().ToLower() } Process { try { switch ($PSCmdlet.ParameterSetName) { Fields { $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserRelation' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$obj.PSObject.Properties.Name -contains $_}) { $obj.$prop = $PSBoundParameters[$prop] } $obj } InputObject { foreach ($iObj in $InputObject) { $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserRelation' foreach ($prop in $iObj.PSObject.Properties.Name | Where-Object {$obj.PSObject.Properties.Name -contains $_ -and $propsToWatch -contains $_}) { $obj.$prop = $iObj.$prop } $obj } } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Add-GSUserRelation' function Add-GSUserSchemaField { <# .SYNOPSIS Builds a UserPhone object to use when creating or updating a Schema .DESCRIPTION Builds a UserPhone object to use when creating or updating a Schema .PARAMETER FieldName The name of the field .PARAMETER FieldType The type of the field. * Acceptable values are: * "BOOL": Boolean values. * "DATE": Dates in ISO-8601 format: http://www.w3.org/TR/NOTE-datetime * "DOUBLE": Double-precision floating-point values. * "EMAIL": Email addresses. * "INT64": 64-bit integer values. * "PHONE": Phone numbers. * "STRING": String values. .PARAMETER Indexed Switch specifying whether the field is indexed or not. Default: true .PARAMETER MultiValued A switch specifying whether this is a multi-valued field or not. Default: false .PARAMETER ReadAccessType Parameter description .PARAMETER InputObject Parameter description .EXAMPLE New-GSUserSchema -SchemaName "SDK" -Fields (Add-GSUserSchemaField -FieldName "string" -FieldType STRING -ReadAccessType ADMINS_AND_SELF),(Add-GSUserSchemaField -FieldName "date" -FieldType DATE -ReadAccessType ADMINS_AND_SELF) This command will create a schema named "SDK" with two fields, "string" and "date", readable by ADMINS_AND_SELF #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.SchemaFieldSpec')] [CmdletBinding(DefaultParameterSetName = "InputObject")] Param ( [Parameter(Mandatory = $false,ParameterSetName = "Fields")] [String] $FieldName, [Parameter(Mandatory = $false,ParameterSetName = "Fields")] [ValidateSet("BOOL","DATE","DOUBLE","EMAIL","INT64","PHONE","STRING")] [String] $FieldType = "STRING", [Parameter(Mandatory = $false,ParameterSetName = "Fields")] [Switch] $Indexed, [Parameter(Mandatory = $false,ParameterSetName = "Fields")] [Switch] $MultiValued, [Parameter(Mandatory = $false,ParameterSetName = "Fields")] [ValidateSet("ADMINS_AND_SELF","ALL_DOMAIN_USERS")] [String] $ReadAccessType = "ADMINS_AND_SELF", [Parameter(Mandatory = $false,ValueFromPipeline = $true,ParameterSetName = "InputObject")] [Google.Apis.Admin.Directory.directory_v1.Data.SchemaFieldSpec[]] $InputObject ) Begin { $propsToWatch = @( 'FieldName' 'FieldType' 'Indexed' 'MultiValued' 'ReadAccessType' ) } Process { try { switch ($PSCmdlet.ParameterSetName) { Fields { $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.SchemaFieldSpec' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$obj.PSObject.Properties.Name -contains $_}) { $obj.$prop = $PSBoundParameters[$prop] } $obj } InputObject { foreach ($iObj in $InputObject) { $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.SchemaFieldSpec' foreach ($prop in $iObj.PSObject.Properties.Name | Where-Object {$obj.PSObject.Properties.Name -contains $_ -and $propsToWatch -contains $_}) { $obj.$prop = $iObj.$prop } $obj } } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Add-GSUserSchemaField' function Block-CoreCLREncryptionWarning { <# .SYNOPSIS Blocks CoreCLR encryption warning from reappearing (specific to PSGSuite) .DESCRIPTION Blocks CoreCLR encryption warning from reappearing (specific to PSGSuite) by creating a blank txt file in the path: ~\.scrthq .EXAMPLE Block-CoreCLREncryptionWarning Creates the breadcrumb file to let PSGSuite know that you've acknowledged the CoreCLR encryption warning and it does not need to be displayed every time the module is imported #> New-Item -Path (Join-Path (Join-Path "~" ".scrthq") "BlockCoreCLREncryptionWarning.txt") -ItemType File -Force | Out-Null } Export-ModuleMember -Function 'Block-CoreCLREncryptionWarning' function Compare-ModuleVersion { <# .SYNOPSIS Compares the installed version of a module with the latest version on the PowerShell Gallery .DESCRIPTION Compares the installed version of a module with the latest version on the PowerShell Gallery .PARAMETER ModuleName The name of the module to compare .EXAMPLE Compare-ModuleVersion PSGSuite #> [CmdletBinding()] Param ( [parameter(Mandatory = $false,Position = 0)] [String[]] $ModuleName = 'PSGSuite' ) Begin { $results = New-Object System.Collections.ArrayList } Process { foreach ($module in $ModuleName) { Write-Verbose "Comparing module versions for module '$module'" $result = [PSCustomObject][Ordered]@{ ModuleName = $module InstalledVersion = $null GalleryVersion = $null UpdateAvailable = $null } if ($InstalledVersion = ((Get-Module -Name $module -ListAvailable).Version | Sort-Object)[-1]) { $result.InstalledVersion = $InstalledVersion $uri = [Uri]"https://www.powershellgallery.com/api/v2/Packages?`$filter=Id eq '$($module)' and IsLatestVersion" if ($GalleryVersion = [System.Version]((Invoke-RestMethod -Uri $uri -Verbose:$false).properties.Version)) { $result.GalleryVersion = $GalleryVersion $result.UpdateAvailable = if ($InstalledVersion -ge $GalleryVersion) { $false } else { $true } } } else { Write-Warning "Module '$module' was not found on this machine; unable to compare module versions." } [void]$results.Add($result) } } End { return $results } } Export-ModuleMember -Function 'Compare-ModuleVersion' function Unblock-CoreCLREncryptionWarning { <# .SYNOPSIS Unblocks CoreCLR encryption warning to ensure it appears if applicable (specific to PSGSuite) .DESCRIPTION Unblocks CoreCLR encryption warning to ensure it appears if applicable (specific to PSGSuite) by removing the following file if it exists: ~\.scrthq\BlockCoreCLREncryptionWarning.txt .EXAMPLE Unblock-CoreCLREncryptionWarning Removes the breadcrumb file to let PSGSuite know that you want to receive the CoreCLR encryption warning if applicable #> if (Test-Path (Join-Path (Join-Path "~" ".scrthq") "BlockCoreCLREncryptionWarning.txt")) { Remove-Item -Path (Join-Path (Join-Path "~" ".scrthq") "BlockCoreCLREncryptionWarning.txt") -Force } } Export-ModuleMember -Function 'Unblock-CoreCLREncryptionWarning' function Get-GSUserLicense { <# .SYNOPSIS Gets the G Suite license information for a user or list of users .DESCRIPTION Gets the G Suite license information for a user or list of users .PARAMETER User The primary email or unique Id of the user to retrieve license information for .PARAMETER License The license SKU to retrieve information for. If excluded, searches all license SKUs .PARAMETER ProductId The product Id to list licenses for .PARAMETER PageSize The page size of the result set .PARAMETER Limit The maximum amount of results you want returned. Exclude or set to 0 to return all results .PARAMETER CheckAll If $true, force a check of all license products when specifying a User. This will return all license types it finds for a specific user instead of the default behavior of short circuiting after matching against the first license assigned. .EXAMPLE Get-GSUserLicense Gets the full list of licenses for the customer #> [OutputType('Google.Apis.Licensing.v1.Data.LicenseAssignment')] [cmdletbinding(DefaultParameterSetName = "List")] Param ( [parameter(Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "Get")] [Alias("PrimaryEmail", "UserKey", "Mail","UserId")] [ValidateNotNullOrEmpty()] [String[]] $User, [parameter(Mandatory = $false, ParameterSetName = "List")] [Alias("MaxResults")] [ValidateRange(1, 1000)] [Int] $PageSize = 1000, [parameter(Mandatory = $false, ParameterSetName = "List")] [Alias('First')] [Int] $Limit = 0, [parameter(ParameterSetName = "Get")] [Switch] $CheckAll ) DynamicParam { $paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary # License $_licenses = (Get-LicenseSkuHash).Keys | Sort-Object -Unique $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $attribute = New-Object System.Management.Automation.ParameterAttribute $attribute.Mandatory = $false $attributeCollection.Add($attribute) $attribute = New-Object System.Management.Automation.AliasAttribute('SkuId') $attributeCollection.Add($attribute) $attribute = New-Object System.Management.Automation.ValidateSetAttribute($_licenses) $attributeCollection.Add($attribute) $Name = 'License' $dynParam = New-Object System.Management.Automation.RuntimeDefinedParameter($Name, [string], $attributeCollection) $paramDictionary.Add($Name, $dynParam) # ProductId $_products = (Get-LicenseProductHash).Keys | Sort-Object -Unique $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $attribute = New-Object System.Management.Automation.ParameterAttribute $attribute.Mandatory = $false $attribute.ParameterSetName = 'List' $attributeCollection.Add($attribute) $attribute = New-Object System.Management.Automation.ValidateSetAttribute($_products) $attributeCollection.Add($attribute) $Name = 'ProductId' $dynParam = New-Object System.Management.Automation.RuntimeDefinedParameter($Name, [string[]], $attributeCollection) $paramDictionary.Add($Name, $dynParam) # return the collection of dynamic parameters return $paramDictionary } Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/apps.licensing' ServiceType = 'Google.Apis.Licensing.v1.LicensingService' } $service = New-GoogleService @serviceParams $License = $PSBoundParameters['License'] $ProductId = if ($PSBoundParameters.ContainsKey('ProductId')) { $PSBoundParameters['ProductId'] } else { (Get-LicenseProductFromDisplayName).Keys | Where-Object {$_ -ne 'Cloud-Identity'} | Sort-Object } } Process { try { switch ($PSCmdlet.ParameterSetName) { Get { foreach ($U in $User) { $response = $null Resolve-Email ([ref]$U) if ($PSBoundParameters.ContainsKey('License')) { Write-Verbose "Getting License SKU '$License' for User '$U'" $License = Get-LicenseSkuFromDisplayName $License $request = $service.LicenseAssignments.Get((Get-LicenseSkuToProductHash $License), $License, $U) $request.Execute() } else { $matchedLicense = $false foreach ($License in (Get-LicenseSkuFromDisplayName).Keys | Sort-Object) { $response = $null Write-Verbose "Getting License SKU '$License' for User '$U'" $License = Get-LicenseSkuFromDisplayName $License try { $request = $service.LicenseAssignments.Get((Get-LicenseSkuToProductHash $License), $License, $U) $response = $request.Execute() } catch {} if (-not $CheckAll -and $response) { $matchedLicense = $true $response break } elseif ($response) { $matchedLicense = $true $response } } if (-not $matchedLicense) { Write-Warning "No license found for $U!" } } } } List { if ($License) { $ProductID = Get-LicenseSkuToProductHash $License } $total = 0 $overLimit = $false foreach ($prodId in $ProductID) { $origProdId = $prodId try { if (-not $overLimit) { Write-Verbose "Retrieving licenses for product '$origProdId'" $prodId = Get-LicenseProductHash $prodId if ($License) { $origLicense = $License $License = Get-LicenseSkuFromDisplayName $License $request = $service.LicenseAssignments.ListForProductAndSku($prodId, $License, $Script:PSGSuite.Domain) } else { $request = $service.LicenseAssignments.ListForProduct($prodId, $Script:PSGSuite.Domain) } 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) $PageSize = $Limit } $request.MaxResults = $PageSize [int]$i = 1 $overLimit = $false do { $result = $request.Execute() $result.Items $total += $result.Items.Count $request.PageToken = $result.NextPageToken [int]$retrieved = ($i + $result.Items.Count) - 1 if ($License) { Write-Verbose "Retrieved $retrieved licenses for product '$origProdId' & sku '$origLicense'..." } else { Write-Verbose "Retrieved $retrieved licenses for product '$origProdId'..." } if ($Limit -gt 0 -and $total -eq $Limit) { Write-Verbose "Limit reached: $Limit" $overLimit = $true } elseif ($Limit -gt 0 -and ($total + $PageSize) -gt $Limit) { $newPS = $Limit - $total Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize, $newPS) $request.MaxResults = $newPS } [int]$i = $i + $result.Items.Count } until ($overLimit -or !$result.NextPageToken) } } catch { if ($_.Exception.Message -notmatch 'Invalid productId') { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } else { Write-Verbose "Retrieved $retrieved licenses for product '$origProdId'..." } } } Write-Verbose "Retrieved $total total licenses" } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSUserLicense' function Remove-GSUserLicense { <# .SYNOPSIS Removes a license assignment from a user .DESCRIPTION Removes a license assignment from a user. Useful for restoring a user from a Vault-Former-Employee to an auto-assigned G Suite Business license by removing the Vault-Former-Employee license, for example. .PARAMETER User The user's current primary email address .PARAMETER License The license SKU to remove from the user .EXAMPLE Remove-GSUserLicense -User joe -License Google-Vault-Former-Employee Removes the Vault-Former-Employee license from Joe #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail","UserId")] [ValidateNotNullOrEmpty()] [string[]] $User ) DynamicParam { $paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary # License $_licenses = (Get-LicenseSkuHash).Keys | Sort-Object -Unique $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $attribute = New-Object System.Management.Automation.ParameterAttribute $attribute.Mandatory = $true $attribute.Position = 1 $attributeCollection.Add($attribute) $attribute = New-Object System.Management.Automation.AliasAttribute('SkuId') $attributeCollection.Add($attribute) $attribute = New-Object System.Management.Automation.ValidateSetAttribute($_licenses) $attributeCollection.Add($attribute) $Name = 'License' $dynParam = New-Object System.Management.Automation.RuntimeDefinedParameter($Name, [string], $attributeCollection) $paramDictionary.Add($Name, $dynParam) return $paramDictionary } Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/apps.licensing' ServiceType = 'Google.Apis.Licensing.v1.LicensingService' } $service = New-GoogleService @serviceParams $License = $PSBoundParameters['License'] } Process { try { foreach ($U in $User) { Resolve-Email ([ref]$U) if ($PSCmdlet.ShouldProcess("Revoking license '$License' from user '$U'")) { Write-Verbose "Revoking license '$License' from user '$U'" $License = Get-LicenseSkuFromDisplayName $License $request = $service.LicenseAssignments.Delete((Get-LicenseSkuToProductHash $License),$License,$U) $request.Execute() Write-Verbose "License revoked for user '$U'" } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Remove-GSUserLicense' function Set-GSUserLicense { <# .SYNOPSIS Sets the license for a user .DESCRIPTION Sets the license for a user .PARAMETER User The user's current primary email address .PARAMETER License The license SKU to set for the user .EXAMPLE Set-GSUserLicense -User joe -License Google-Apps-For-Business Sets Joe to a Google-Apps-For-Business license #> [OutputType('Google.Apis.Licensing.v1.Data.LicenseAssignment')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail","UserId")] [ValidateNotNullOrEmpty()] [String[]] $User ) DynamicParam { $paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary # License $_licenses = (Get-LicenseSkuHash).Keys | Sort-Object -Unique $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $attribute = New-Object System.Management.Automation.ParameterAttribute $attribute.Mandatory = $true $attribute.Position = 1 $attributeCollection.Add($attribute) $attribute = New-Object System.Management.Automation.AliasAttribute('SkuId') $attributeCollection.Add($attribute) $attribute = New-Object System.Management.Automation.ValidateSetAttribute($_licenses) $attributeCollection.Add($attribute) $Name = 'License' $dynParam = New-Object System.Management.Automation.RuntimeDefinedParameter($Name, [string], $attributeCollection) $paramDictionary.Add($Name, $dynParam) return $paramDictionary } Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/apps.licensing' ServiceType = 'Google.Apis.Licensing.v1.LicensingService' } $service = New-GoogleService @serviceParams $License = $PSBoundParameters['License'] } Process { try { foreach ($U in $User) { Resolve-Email ([ref]$U) Write-Verbose "Setting license for $U to $License" $License = Get-LicenseSkuFromDisplayName $License $body = New-Object 'Google.Apis.Licensing.v1.Data.LicenseAssignmentInsert' -Property @{ UserId = $U } $request = $service.LicenseAssignments.Insert($body,(Get-LicenseSkuToProductHash $License),$License) $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Set-GSUserLicense' function Update-GSUserLicense { <# .SYNOPSIS Reassign a user's product SKU with a different SKU in the same product .DESCRIPTION Reassign a user's product SKU with a different SKU in the same product .PARAMETER User The user's current primary email address .PARAMETER License The license SKU that you would like to reassign the user to .EXAMPLE Update-GSUserLicense -User joe -License G-Suite-Enterprise Updates Joe to a G-Suite-Enterprise license #> [OutputType('Google.Apis.Licensing.v1.Data.LicenseAssignment')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail","UserId")] [ValidateNotNullOrEmpty()] [String[]] $User ) DynamicParam { $paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary # License $_licenses = (Get-LicenseSkuHash).Keys | Sort-Object -Unique $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $attribute = New-Object System.Management.Automation.ParameterAttribute $attribute.Mandatory = $false $attribute.Position = 1 $attributeCollection.Add($attribute) $attribute = New-Object System.Management.Automation.AliasAttribute('SkuId') $attributeCollection.Add($attribute) $attribute = New-Object System.Management.Automation.ValidateSetAttribute($_licenses) $attributeCollection.Add($attribute) $Name = 'License' $dynParam = New-Object System.Management.Automation.RuntimeDefinedParameter($Name, [string], $attributeCollection) $paramDictionary.Add($Name, $dynParam) return $paramDictionary } Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/apps.licensing' ServiceType = 'Google.Apis.Licensing.v1.LicensingService' } $service = New-GoogleService @serviceParams $License = $PSBoundParameters['License'] } Process { try { foreach ($U in $User) { Resolve-Email ([ref]$U) Write-Verbose "Setting license for $U to $License" $License = Get-LicenseSkuFromDisplayName $License $body = Get-GSUserLicense -User $U $request = $service.LicenseAssignments.Update($body,(Get-LicenseSkuToProductHash $License),$License,$U) $request.Execute() } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Update-GSUserLicense' function Get-GSOrganizationalUnit { <# .SYNOPSIS Gets Organizational Unit information .DESCRIPTION Gets Organizational Unit information .PARAMETER SearchBase The OrgUnitPath you would like to search for. This can be the single OrgUnit to return or the top level of which to return children of .PARAMETER SearchScope The depth at which to return the list of OrgUnits children Available values are: * "Base": only return the OrgUnit specified in the SearchBase * "Subtree": return the full list of OrgUnits underneath the specified SearchBase * "OneLevel": return the SearchBase and the OrgUnit's directly underneath it * "All": same as Subtree * "Children": same as OneLevel Defaults to 'All' .EXAMPLE Get-GSOrganizationalUnit -SearchBase "/" -SearchScope Base Gets the top level Organizational Unit information #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.OrgUnit')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0)] [Alias('OrgUnitPath','BaseOrgUnitPath')] [String] $SearchBase, [parameter(Mandatory = $false)] [Alias('Type')] [ValidateSet('Base','Subtree','OneLevel','All','Children')] [String] $SearchScope = 'All' ) Begin { if ($PSBoundParameters.Keys -contains 'SearchBase' -and $SearchBase -ne "/" -and $SearchScope -eq 'Base') { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.orgunit' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } } Process { try { if ($PSBoundParameters.Keys -contains 'SearchBase' -and $SearchBase -ne "/" -and $SearchScope -eq 'Base') { foreach ($O in $SearchBase) { Write-Verbose "Getting Organizational Unit '$O'" $O = $O.TrimStart('/') $request = $service.Orgunits.Get($Script:PSGSuite.CustomerId,([Google.Apis.Util.Repeatable[String]]::new([String[]]$O))) $request.Execute() } } elseif ($SearchBase -eq "/" -and $SearchScope -eq 'Base') { $topId = Get-GSOrganizationalUnitListPrivate -SearchBase "/" -Type Children -Verbose:$false | Where-Object {$_.ParentOrgUnitPath -eq "/"} | Select-Object -ExpandProperty ParentOrgUnitId -Unique Get-GSOrganizationalUnit -OrgUnitPath $topId -SearchScope Base } else { Get-GSOrganizationalUnitListPrivate @PSBoundParameters } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSOrganizationalUnit' function New-GSOrganizationalUnit { <# .SYNOPSIS Creates a new OrgUnit .DESCRIPTION Creates a new Organizational Unit .PARAMETER Name The name of the new OrgUnit .PARAMETER ParentOrgUnitPath The path of the parent OrgUnit Defaults to "/" (the root OrgUnit) .PARAMETER ParentOrgUnitId The unique ID of the parent organizational unit. .PARAMETER Description Description of the organizational unit. .PARAMETER BlockInheritance Determines if a sub-organizational unit can inherit the settings of the parent organization. The default value is false, meaning a sub-organizational unit inherits the settings of the nearest parent organizational unit. For more information on inheritance and users in an organization structure, see the administration help center: http://support.google.com/a/bin/answer.py?hl=en&answer=182442&topic=1227584&ctx=topic .EXAMPLE New-GSOrganizationalUnit -Name "Test Org" -ParentOrgUnitPath "/Testing" -Description "This is a test OrgUnit" Creates a new OrgUnit named "Test Org" underneath the existing org unit path "/Testing" with the description "This is a test OrgUnit" #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.OrgUnit')] [cmdletbinding(DefaultParameterSetName = 'ParentOrgUnitPath')] Param ( [parameter(Mandatory = $true)] [String] $Name, [parameter(Mandatory = $false,ParameterSetName = 'ParentOrgUnitPath')] [string] $ParentOrgUnitPath, [parameter(Mandatory = $false,ParameterSetName = 'ParentOrgUnitId')] [string] $ParentOrgUnitId, [parameter(Mandatory = $false)] [String] $Description, [parameter(Mandatory = $false)] [Switch] $BlockInheritance ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.orgunit' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams if ($PSBoundParameters.Keys -notcontains 'ParentOrgUnitPath' -and $PSCmdlet.ParameterSetName -eq 'ParentOrgUnitPath') { $PSBoundParameters['ParentOrgUnitPath'] = '/' } } Process { try { Write-Verbose "Creating OrgUnit '$Name'" $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.OrgUnit' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) { switch ($prop) { Default { $body.$prop = $PSBoundParameters[$prop] } } } $request = $service.Orgunits.Insert($body,$Script:PSGSuite.CustomerId) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'New-GSOrganizationalUnit' function Remove-GSOrganizationalUnit { <# .SYNOPSIS Removes an OrgUnit .DESCRIPTION Removes an Organization Unit .PARAMETER OrgUnitPath The path of the OrgUnit you would like to Remove-GSOrganizationalUnit .EXAMPLE Remove-GSOrganizationalUnit -OrgUnitPath "/Testing" Removes the OrgUnit "/Testing" on confirmation #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $true,Position = 0)] [String[]] $OrgUnitPath ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.orgunit' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { foreach ($O in $OrgUnitPath) { if ($PSCmdlet.ShouldProcess("Deleting OrgUnit at Path '$O'")) { Write-Verbose "Deleting OrgUnit at Path '$O'" $O = $O.TrimStart('/') $request = $service.Orgunits.Delete($Script:PSGSuite.CustomerId,([Google.Apis.Util.Repeatable[String]]::new([String[]]$O))) $request.Execute() } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Remove-GSOrganizationalUnit' function Update-GSOrganizationalUnit { <# .SYNOPSIS Updates an OrgUnit .DESCRIPTION Updates an Organizational Unit .PARAMETER OrgUnitID The unique Id of the OrgUnit to update .PARAMETER OrgUnitPath The path of the OrgUnit to update .PARAMETER Name The new name for the OrgUnit .PARAMETER ParentOrgUnitId The new Parent ID for the OrgUnit .PARAMETER ParentOrgUnitPath The path of the new Parent for the OrgUnit .PARAMETER Description The new description for the OrgUnit .PARAMETER BlockInheritance Determines if a sub-organizational unit can inherit the settings of the parent organization. The default value is false, meaning a sub-organizational unit inherits the settings of the nearest parent organizational unit. For more information on inheritance and users in an organization structure, see the administration help center: http://support.google.com/a/bin/answer.py?hl=en&answer=182442&topic=1227584&ctx=topic .EXAMPLE Update-GSOrganizationalUnit -OrgUnitPath "/Testing" -Name "Testing More" -Description "Doing some more testing" Updates the OrgUnit '/Testing' with a new name "Testing More" and new description "Doing some more testing" #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.OrgUnit')] [cmdletbinding(DefaultParameterSetName = "Id")] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true,ParameterSetName = "Id")] [ValidateNotNullOrEmpty()] [String] $OrgUnitID, [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true,ParameterSetName = "Path")] [ValidateNotNullOrEmpty()] [String] $OrgUnitPath, [parameter(Mandatory = $false)] [String] $Name, [parameter(Mandatory = $false)] [string] $ParentOrgUnitId, [parameter(Mandatory = $false)] [string] $ParentOrgUnitPath, [parameter(Mandatory = $false)] [String] $Description, [parameter(Mandatory = $false)] [Switch] $BlockInheritance ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.orgunit' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { $body = switch ($PSCmdlet.ParameterSetName) { Path { Get-GSOrganizationalUnit -SearchBase $OrgUnitPath -SearchScope Base -Verbose:$false } Id { if ($OrgUnitID -notmatch '^id\:') { $OrgUnitID = "id:$OrgUnitID" } Get-GSOrganizationalUnit -SearchBase $OrgUnitID -SearchScope Base -Verbose:$false } } if ($ParentOrgUnitPath) { $body.ParentOrgUnitId = $(Get-GSOrganizationalUnit -OrgUnitPath $ParentOrgUnitPath -Verbose:$false | Select-Object -ExpandProperty OrgUnitID) } elseif ($ParentOrgUnitId) { $body.ParentOrgUnitPath = $(Get-GSOrganizationalUnit -OrgUnitPath $ParentOrgUnitId -Verbose:$false | Select-Object -ExpandProperty OrgUnitPath) } Write-Verbose "Updating OrgUnit '$OrgUnitPath'" foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_ -and $_ -ne 'OrgUnitPath'}) { $body.$prop = $PSBoundParameters[$prop] } $trimPath = $body.OrgUnitPath.TrimStart('/') $request = $service.Orgunits.Patch($body,$Script:PSGSuite.CustomerId,([Google.Apis.Util.Repeatable[String]]::new([String[]]$trimPath))) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Update-GSOrganizationalUnit' function Get-GSActivityReport { <# .SYNOPSIS Retrieves a list of activities .DESCRIPTION Retrieves a list of activities .PARAMETER UserKey Represents the profile id or the user email for which the data should be filtered. When 'all' is specified as the userKey, it returns usageReports for all users .PARAMETER ApplicationName Application name for which the events are to be retrieved. Available values are: * "Admin": The Admin console application's activity reports return account information about different types of administrator activity events. * "Calendar": The G Suite Calendar application's activity reports return information about various Calendar activity events. * "Drive": The Google Drive application's activity reports return information about various Google Drive activity events. The Drive activity report is only available for G Suite Business customers. * "Groups": The Google Groups application's activity reports return information about various Groups activity events. * "GPlus": The Google+ application's activity reports return information about various Google+ activity events. * "Login": The G Suite Login application's activity reports return account information about different types of Login activity events. * "Mobile": The G Suite Mobile Audit activity report return information about different types of Mobile Audit activity events. * "Rules": The G Suite Rules activity report return information about different types of Rules activity events. * "Token": The G Suite Token application's activity reports return account information about different types of Token activity events. Defaults to "Admin" .PARAMETER EventName The name of the event being queried .PARAMETER ActorIpAddress IP Address of host where the event was performed. Supports both IPv4 and IPv6 addresses .PARAMETER StartTime Return events which occurred after this time .PARAMETER EndTime Return events which occurred at or before this time .PARAMETER Filters Event parameters in the form [parameter1 name][operator][parameter1 value] .PARAMETER PageSize Number of activity records to be shown in each page .PARAMETER Limit The maximum amount of results you want returned. Exclude or set to 0 to return all results .EXAMPLE Get-GSActivityReport -StartTime (Get-Date).AddDays(-30) Gets the admin activity report for the last 30 days #> [OutputType('Google.Apis.Admin.Reports.reports_v1.Data.Activity')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0)] [ValidateNotNullOrEmpty()] [String] $UserKey = 'all', [parameter(Mandatory = $false,Position = 1)] [ValidateSet("Admin","Calendar","Drive","Groups","GPlus","Login","Mobile","Rules","Token")] [String] $ApplicationName = "Admin", [parameter(Mandatory = $false,Position = 2)] [String] $EventName, [parameter(Mandatory = $false)] [DateTime] $StartTime, [parameter(Mandatory = $false)] [DateTime] $EndTime, [parameter(Mandatory = $false)] [String] $ActorIpAddress, [parameter(Mandatory = $false)] [String[]] $Filters, [parameter(Mandatory = $false)] [ValidateRange(1,1000)] [Alias("MaxResults")] [Int] $PageSize = "1000", [parameter(Mandatory = $false)] [Alias('First')] [Int] $Limit = 0 ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.reports.audit.readonly' ServiceType = 'Google.Apis.Admin.Reports.reports_v1.ReportsService' } $service = New-GoogleService @serviceParams } Process { try { if ($UserKey -notlike "*@*.*" -and $UserKey -cne 'all') { $UserKey = "$($UserKey)@$($Script:PSGSuite.Domain)" } Write-Verbose "Getting $ApplicationName Activity report" $request = $service.Activities.List($UserKey,($ApplicationName.ToLower())) 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) $PageSize = $Limit } $request.MaxResults = $PageSize foreach ($key in $PSBoundParameters.Keys | Where-Object {$_ -notin @('UserKey','ApplicationName')}) { switch ($key) { StartTime { $request.$key = $StartTime.ToString('o') } EndTime { $request.$key = $EndTime.ToString('o') } Filters { $request.$key = $PSBoundParameters[$key] -join "," } Default { if ($request.PSObject.Properties.Name -contains $key) { $request.$key = $PSBoundParameters[$key] } } } } [int]$i = 1 $overLimit = $false do { $result = $request.Execute() $result.Items $request.PageToken = $result.NextPageToken [int]$retrieved = ($i + $result.Items.Count) - 1 Write-Verbose "Retrieved $retrieved events..." if ($Limit -gt 0 -and $retrieved -eq $Limit) { Write-Verbose "Limit reached: $Limit" $overLimit = $true } elseif ($Limit -gt 0 -and ($retrieved + $PageSize) -gt $Limit) { $newPS = $Limit - $retrieved Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize,$newPS) $request.MaxResults = $newPS } [int]$i = $i + $result.Items.Count } until ($overLimit -or !$result.NextPageToken) } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSActivityReport' function Get-GSUsageReport { <# .SYNOPSIS Retrieves the usage report for the specified type. Defaults to Customer Usage Report type. .DESCRIPTION Retrieves the usage report for the specified type. Defaults to Customer Usage Report type. .PARAMETER Date Represents the date for which the data is to be fetched. Defaults to 3 days before the current date. .PARAMETER UserKey [User Usage Report] Represents the profile id or the user email for which the data should be filtered. Use 'all' to retrieve the report for all users. .PARAMETER EntityType [Entity Usage Report] Type of object. Should be one of: * gplus_communities .PARAMETER EntityKey [Entity Usage Report] Represents the key of object for which the data should be filtered. Use 'all' to retrieve the report for all users. .PARAMETER Filters Represents the set of filters including parameter operator value .PARAMETER Parameters Represents the application name, parameter name pairs to fetch in csv as app_name1:param_name1 .PARAMETER PageSize Maximum number of results to return. Maximum allowed is 1000 .PARAMETER Limit The maximum amount of results you want returned. Exclude or set to 0 to return all results .PARAMETER Flat If $true, returns a flattened object for easy parsing. .PARAMETER Raw If $true, returns the raw, unformatted results. .EXAMPLE Get-GSUsageReport -Date (Get-Date).AddDays(-30) Gets the Customer Usage report from 30 days prior #> [cmdletbinding(DefaultParameterSetName = "Customer")] Param ( [parameter(Mandatory = $false,ParameterSetName = "Customer")] [parameter(Mandatory = $false,ParameterSetName = "Entity")] [parameter(Mandatory = $false,ParameterSetName = "User")] [DateTime] $Date = (Get-Date).AddDays(-3), [parameter(Mandatory = $true,ParameterSetName = "User")] [ValidateNotNullOrEmpty()] [String] $UserKey, [parameter(Mandatory = $true,ParameterSetName = "Entity")] [ValidateSet('gplus_communities')] [String] $EntityType, [parameter(Mandatory = $false,ParameterSetName = "Entity")] [ValidateNotNullOrEmpty()] [String] $EntityKey = 'all', [parameter(Mandatory = $false,ParameterSetName = "Entity")] [parameter(Mandatory = $false,ParameterSetName = "User")] [String[]] $Filters, [parameter(Mandatory = $false)] [String[]] $Parameters, [parameter(Mandatory = $false,ParameterSetName = "Entity")] [parameter(Mandatory = $false,ParameterSetName = "User")] [ValidateRange(1,1000)] [Alias("MaxResults")] [Int] $PageSize = "1000", [parameter(Mandatory = $false,ParameterSetName = "Entity")] [parameter(Mandatory = $false,ParameterSetName = "User")] [Alias('First')] [Int] $Limit = 0, [parameter(Mandatory = $false)] [switch] $Flat, [parameter(Mandatory = $false)] [switch] $Raw ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.reports.usage.readonly' ServiceType = 'Google.Apis.Admin.Reports.reports_v1.ReportsService' } $service = New-GoogleService @serviceParams } Process { try { Write-Verbose "Getting $($PSCmdlet.ParameterSetName) Usage report for $($Date.ToString('yyyy-MM-dd'))" switch ($PSCmdlet.ParameterSetName) { Customer { $request = $service.CustomerUsageReports.Get(($Date.ToString('yyyy-MM-dd'))) } Entity { $request = $service.EntityUsageReports.Get($EntityType,$EntityKey,($Date.ToString('yyyy-MM-dd'))) 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) $PageSize = $Limit } $request.MaxResults = $PageSize } User { if ($UserKey -ceq 'me') { $UserKey = $Script:PSGSuite.AdminEmail } elseif ($UserKey -notlike "*@*.*" -and $UserKey -ne 'all') { $UserKey = "$($UserKey)@$($Script:PSGSuite.Domain)" } $request = $service.UserUsageReport.Get($UserKey,($Date.ToString('yyyy-MM-dd'))) 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) $PageSize = $Limit } $request.MaxResults = $PageSize } } foreach ($key in $PSBoundParameters.Keys | Where-Object {$_ -notin @('Date','UserKey','EntityKey','EntityType')}) { switch ($key) { Filters { $request.$key = $PSBoundParameters[$key] -join "," } Parameters { $request.$key = $PSBoundParameters[$key] -join "," } Default { if ($request.PSObject.Properties.Name -contains $key) { $request.$key = $PSBoundParameters[$key] } } } } $warnings = @() [int]$i = 1 $overLimit = $false do { $result = $request.Execute() if ($Raw) { $result.UsageReportsValue } else { $result.UsageReportsValue | ForEach-Object { if ($null -ne $_) { $orig = $_ $orig | Add-Member -MemberType NoteProperty -Name CustomerId -Value $orig.Entity.CustomerId -Force -PassThru | Add-Member -MemberType NoteProperty -Name EntityType -Value $orig.Entity.Type -Force switch ($PSCmdlet.ParameterSetName) { Entity { $orig | Add-Member -MemberType NoteProperty -Name EntityKey -Value $orig.Entity.EntityKey -Force $orig | Add-Member -MemberType NoteProperty -Name CommunityName -Value $orig.Parameters[$orig.Parameters.Name.IndexOf('gplus:community_name')].StringValue -Force } User { $orig | Add-Member -MemberType NoteProperty -Name Email -Value $orig.Entity.UserEmail -Force -PassThru | Add-Member -MemberType NoteProperty -Name UserEmail -Value $orig.Entity.UserEmail -Force -PassThru | Add-Member -MemberType NoteProperty -Name ProfileId -Value $orig.Entity.ProfileId -Force } } foreach ($param in $orig.Parameters | Sort-Object Name) { if ($null -ne $param.Name) { $paramValue = if ($null -ne $param.StringValue) { $param.StringValue } elseif ($null -ne $param.IntValue) { $param.IntValue } elseif ($null -ne $param.DatetimeValue) { $param.DatetimeValue } elseif ($null -ne $param.BoolValue) { $param.BoolValue } elseif ($null -ne $param.MsgValue) { $param.MsgValue } else { $null } if ($Flat) { $orig | Add-Member -MemberType NoteProperty -Name $param.Name -Value $paramValue -Force } else { $pName = $param.Name -split ":" if ($orig.PSObject.Properties.Name -notcontains $pName[0]) { $orig | Add-Member -MemberType NoteProperty -Name $pName[0] -Value $([Ordered]@{}) -Force } $orig.$($pName[0])[$pName[1]] = $paramValue } } } $orig } } } $warnings += $result.Warnings $request.PageToken = $result.NextPageToken [int]$retrieved = ($i + $result.UsageReportsValue.Count) - 1 Write-Verbose "Retrieved $retrieved entities for this report..." if ($Limit -gt 0 -and $retrieved -eq $Limit) { Write-Verbose "Limit reached: $Limit" $overLimit = $true } elseif ($Limit -gt 0 -and ($retrieved + $PageSize) -gt $Limit) { $newPS = $Limit - $retrieved Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize,$newPS) $request.MaxResults = $newPS } [int]$i = $i + $result.UsageReportsValue.Count } until ($overLimit -or !$result.NextPageToken) if ($warnings | Where-Object {$_.Code}) { $warnings | ForEach-Object { Write-Warning "[$($_.Code)] $($_.Message)" } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSUsageReport' function Get-GSResource { <# .SYNOPSIS Gets Calendar Resources (Calendars, Buildings & Features supported) .DESCRIPTION Gets Calendar Resources (Calendars, Buildings & Features supported) .PARAMETER Id If Id is provided, gets the Resource by Id .PARAMETER Resource The Resource Type to List Available values are: * "Calendars": resource calendars (legacy and new - i.e. conference rooms) * "Buildings": new Building Resources (i.e. "Building A" or "North Campus") * "Features": new Feature Resources (i.e. "Video Conferencing" or "Projector") .PARAMETER Filter String query used to filter results. Should be of the form "field operator value" where field can be any of supported fields and operators can be any of supported operations. Operators include '=' for exact match and ':' for prefix match or HAS match where applicable. For prefix match, the value should always be followed by a *. Supported fields include generatedResourceName, name, buildingId, featureInstances.feature.name. For example buildingId=US-NYC-9TH AND featureInstances.feature.name:Phone. PowerShell filter syntax here is supported as "best effort". Please use Google's filter operators and syntax to ensure best results .PARAMETER OrderBy Field(s) to sort results by in either ascending or descending order. Supported fields include resourceId, resourceName, capacity, buildingId, and floorName. .PARAMETER PageSize Page size of the result set .PARAMETER Limit The maximum amount of results you want returned. Exclude or set to 0 to return all results .EXAMPLE Get-GSResource -Resource Buildings Gets the full list of Buildings Resources #> [CmdletBinding(DefaultParameterSetName = "List")] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = "Get")] [String[]] $Id, [parameter(Mandatory = $false,Position = 1)] [ValidateSet('Calendars','Buildings','Features')] [String] $Resource = 'Calendars', [parameter(Mandatory = $false,ParameterSetName = "List")] [Alias('Query')] [String[]] $Filter, [parameter(Mandatory = $false,ParameterSetName = "List")] [String[]] $OrderBy, [parameter(Mandatory = $false,ParameterSetName = "List")] [ValidateRange(1,500)] [Alias("MaxResults")] [Int] $PageSize = 500, [parameter(Mandatory = $false,ParameterSetName = "List")] [Alias('First')] [Int] $Limit = 0 ) Begin { if ($MyInvocation.InvocationName -eq 'Get-GSCalendarResourceList') { $Resource = 'Calendars' } $propHash = @{ Calendars = 'Items' Buildings = 'BuildingsValue' Features = 'FeaturesValue' } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.resource.calendar' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams $customerId = if ($Script:PSGSuite.CustomerID) { $Script:PSGSuite.CustomerID } else { 'my_customer' } } Process { switch ($PSCmdlet.ParameterSetName) { Get { foreach ($I in $Id) { try { Write-Verbose "Getting Resource $Resource Id '$I'" $request = $service.Resources.$Resource.Get($customerId,$I) $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Id' -Value $I -PassThru | Add-Member -MemberType NoteProperty -Name 'Resource' -Value $Resource -PassThru | Add-Member -MemberType ScriptMethod -Name ToString -Value { $this.Id } -PassThru -Force } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } List { try { $request = $service.Resources.$Resource.List($customerId) 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) $PageSize = $Limit } $request.MaxResults = $PageSize if ($Resource -eq 'Calendars') { if ($OrderBy) { $request.OrderBy = "$($OrderBy -join ", ")" } if ($Filter) { if ($Filter -eq '*') { $Filter = "" } else { $Filter = "$($Filter -join " ")" } $Filter = $Filter -replace " -eq ","=" -replace " -like ",":" -replace " -match ",":" -replace " -contains ",":" -creplace "'True'","True" -creplace "'False'","False" $request.Query = $Filter.Trim() Write-Verbose "Getting Resource $Resource matching filter: `"$($Filter.Trim())`"" } else { Write-Verbose "Getting all Resource $Resource" } } else { Write-Verbose "Getting all Resource $Resource" } [int]$i = 1 $overLimit = $false do { $result = $request.Execute() $result.$($propHash[$Resource]) | ForEach-Object { $obj = $_ $_Id = switch ($Resource) { Calendars { $obj.ResourceId } Buildings { $obj.BuildingId } Features { $obj.Name } } $_ | Add-Member -MemberType NoteProperty -Name 'Id' -Value $_Id -PassThru | Add-Member -MemberType NoteProperty -Name 'Resource' -Value $Resource -PassThru | Add-Member -MemberType ScriptMethod -Name ToString -Value { $this.Id } -PassThru -Force } if ($result.NextPageToken) { $request.PageToken = $result.NextPageToken } [int]$retrieved = ($i + $result.$($propHash[$Resource]).Count) - 1 Write-Verbose "Retrieved $retrieved resources..." if ($Limit -gt 0 -and $retrieved -eq $Limit) { Write-Verbose "Limit reached: $Limit" $overLimit = $true } elseif ($Limit -gt 0 -and ($retrieved + $PageSize) -gt $Limit) { $newPS = $Limit - $retrieved Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize,$newPS) $request.MaxResults = $newPS } [int]$i = $i + $result.$($propHash[$Resource]).Count } until ($overLimit -or !$result.NextPageToken) } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } } Export-ModuleMember -Function 'Get-GSResource' function New-GSResource { <# .SYNOPSIS Creates a new Calendar Resource .DESCRIPTION Creates a new Calendar Resource. Supports Resource types 'Calendars','Buildings' & 'Features' .PARAMETER Name The name of the new Resource .PARAMETER Id The unique ID for the calendar resource. .PARAMETER BuildingId Unique ID for the building a resource is located in. .PARAMETER Description Description of the resource, visible only to admins. .PARAMETER Capacity Capacity of a resource, number of seats in a room. .PARAMETER FloorName Name of the floor a resource is located on (Calendars Resource type) .PARAMETER FloorNames The names of the floors in the building (Buildings Resource type) .PARAMETER FloorSection Name of the section within a floor a resource is located in. .PARAMETER Category The category of the calendar resource. Either CONFERENCE_ROOM or OTHER. Legacy data is set to CATEGORY_UNKNOWN. Acceptable values are: * "CATEGORY_UNKNOWN" * "CONFERENCE_ROOM" * "OTHER" Defaults to 'CATEGORY_UNKNOWN' if creating a Calendar Resource .PARAMETER ResourceType The type of the calendar resource, intended for non-room resources. .PARAMETER UserVisibleDescription Description of the resource, visible to users and admins. .PARAMETER Resource The resource type you would like to create Available values are: * "Calendars": create a Resource Calendar or legacy resource type * "Buildings": create a Resource Building * "Features": create a Resource Feature .EXAMPLE New-GSResource -Name "Training Room" -Id "Train01" -Capacity 75 -Category 'CONFERENCE_ROOM' -ResourceType "Conference Room" -Description "Training room for new hires - has 1 LAN port per station" -UserVisibleDescription "Training room for new hires" Creates a new training room Resource Calendar that will be bookable on Google Calendar #> [CmdletBinding(DefaultParameterSetName = 'Features')] Param ( [parameter(Mandatory = $true,Position = 0)] [String] $Name, [parameter(Mandatory = $false,ParameterSetName = 'Calendars')] [Alias('ResourceId')] [String] $Id, [parameter(Mandatory = $false,ParameterSetName = 'Calendars')] [parameter(Mandatory = $false,ParameterSetName = 'Buildings')] [String] $BuildingId, [parameter(Mandatory = $false,ParameterSetName = 'Calendars')] [parameter(Mandatory = $false,ParameterSetName = 'Buildings')] [String] $Description, [parameter(Mandatory = $false,ParameterSetName = 'Calendars')] [Int] $Capacity, [parameter(Mandatory = $false,ParameterSetName = 'Calendars')] [String] $FloorName, [parameter(Mandatory = $false,ParameterSetName = 'Buildings')] [String[]] $FloorNames, [parameter(Mandatory = $false,ParameterSetName = 'Calendars')] [String] $FloorSection, [parameter(Mandatory = $false,ParameterSetName = 'Calendars')] [ValidateSet('CATEGORY_UNKNOWN','CONFERENCE_ROOM','OTHER')] [String] $Category, [parameter(Mandatory = $false,ParameterSetName = 'Calendars')] [String] $ResourceType, [parameter(Mandatory = $false,ParameterSetName = 'Calendars')] [String] $UserVisibleDescription, [parameter(Mandatory = $false)] [ValidateSet('Calendars','Buildings','Features')] [String] $Resource ) Begin { $resType = if ($MyInvocation.InvocationName -eq 'New-GSCalendarResource') { 'Calendars' } elseif ($Resource) { $Resource } else { $PSCmdlet.ParameterSetName } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.resource.calendar' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams if ($PSBoundParameters -notcontains 'Category' -and $PSCmdlet.ParameterSetName -eq 'Calendars') { $PSBoundParameters['Category'] = 'CATEGORY_UNKNOWN' } } Process { try { Write-Verbose "Creating Resource $resType '$Name'" $body = New-Object "$(switch ($resType) { Calendars { 'Google.Apis.Admin.Directory.directory_v1.Data.CalendarResource' } Buildings { 'Google.Apis.Admin.Directory.directory_v1.Data.Building' } Features { 'Google.Apis.Admin.Directory.directory_v1.Data.Feature' } })" foreach ($key in $PSBoundParameters.Keys) { switch ($key) { Category { $body.ResourceCategory = $PSBoundParameters[$key] } Id { if ($resType -eq 'Calendars') { $body.ResourceId = $PSBoundParameters[$key] } else { $body.$key = $PSBoundParameters[$key] } } Name { if ($resType -eq 'Calendars') { $body.ResourceName = $PSBoundParameters[$key] } elseif ($resType -eq 'Buildings') { $body.BuildingName = $PSBoundParameters[$key] } else { $body.$key = $PSBoundParameters[$key] } } Description { if ($resType -eq 'Calendars') { $body.ResourceDescription = $PSBoundParameters[$key] } else { $body.$key = $PSBoundParameters[$key] } } Default { if ($body.PSObject.Properties.Name -contains $key) { $body.$key = $PSBoundParameters[$key] } } } } $request = $service.Resources.$resType.Insert($body,$(if ($Script:PSGSuite.CustomerID) { $Script:PSGSuite.CustomerID } else { 'my_customer' })) $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Resource' -Value $resType -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'New-GSResource' function Remove-GSResource { <# .SYNOPSIS Removes a resource .DESCRIPTION Removes a resource .PARAMETER ResourceId The Resource Id of the Resource *Calendar* you would like to remove .PARAMETER BuildingId The Building Id of the Resource *Building* you would like to remove .PARAMETER FeatureKey The Feature Key of the Resource *Feature* you would like to remove .EXAMPLE Remove-GSResource -ResourceId Train01 Removes the Resource Calendar 'Train01' after confirmation #> [CmdletBinding(SupportsShouldProcess = $true,ConfirmImpact = "High",DefaultParameterSetName = 'Calendars')] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true,ParameterSetName = 'Calendars')] [Alias('CalendarResourceId')] [String[]] $ResourceId, [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true,ParameterSetName = 'Buildings')] [String[]] $BuildingId, [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true,ParameterSetName = 'Features')] [Alias('Name')] [String[]] $FeatureKey ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.resource.calendar' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { switch ($PSCmdlet.ParameterSetName) { Calendars { $Resource = 'Calendars' $list = $ResourceId } Buildings { $Resource = 'Buildings' $list = $BuildingId } Features { $Resource = 'Features' $list = $FeatureKey } } foreach ($I in $list) { if ($PSCmdlet.ShouldProcess("Deleting Resource $Resource Id '$I'")) { Write-Verbose "Deleting Resource $Resource Id '$I'" $request = $service.Resources.$Resource.Delete($(if($Script:PSGSuite.CustomerID){$Script:PSGSuite.CustomerID}else{'my_customer'}),$I) $request.Execute() Write-Verbose "Resource $Resource Id '$I' has been successfully deleted" } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Remove-GSResource' function Update-GSResource { <# .SYNOPSIS Updates a Calendar Resource .DESCRIPTION Updates a Calendar Resource .PARAMETER ResourceId The unique Id of the Resource Calendar that you would like to update .PARAMETER BuildingId If updating a Resource Building, the unique Id of the building you would like to update If updating a Resource Calendar, the new Building Id for the resource .PARAMETER FeatureKey The unique key of the Feature you would like to update .PARAMETER Name The new name of the resource .PARAMETER Description Description of the resource, visible only to admins. .PARAMETER Capacity Capacity of a resource, number of seats in a room. .PARAMETER FloorName Name of the floor a resource is located on (Calendars Resource type) .PARAMETER FloorNames The names of the floors in the building (Buildings Resource type) .PARAMETER FloorSection Name of the section within a floor a resource is located in. .PARAMETER Category The new category of the calendar resource. Either CONFERENCE_ROOM or OTHER. Legacy data is set to CATEGORY_UNKNOWN. Acceptable values are: * "CATEGORY_UNKNOWN" * "CONFERENCE_ROOM" * "OTHER" Defaults to 'CATEGORY_UNKNOWN' if creating a Calendar Resource .PARAMETER ResourceType The type of the calendar resource, intended for non-room resources. .PARAMETER UserVisibleDescription Description of the resource, visible to users and admins. .PARAMETER Resource The resource type you would like to update Available values are: * "Calendars": create a Resource Calendar or legacy resource type * "Buildings": create a Resource Building * "Features": create a Resource Feature .EXAMPLE Update-GSResource -ResourceId Train01 -Name 'Corp Training Room' Updates the calendar resource with ID 'Train01' to the new name 'Corp Training Room' #> [CmdletBinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true,ParameterSetName = 'Calendars')] [Alias('CalendarResourceId','Id')] [String] $ResourceId, [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true,ParameterSetName = 'Buildings')] [parameter(Mandatory = $false,ParameterSetName = 'Calendars')] [String] $BuildingId, [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true,ParameterSetName = 'Features')] [String] $FeatureKey, [parameter(Mandatory = $false,Position = 0)] [String] $Name, [parameter(Mandatory = $false,ParameterSetName = 'Calendars')] [parameter(Mandatory = $false,ParameterSetName = 'Buildings')] [String] $Description, [parameter(Mandatory = $false,ParameterSetName = 'Calendars')] [Int] $Capacity, [parameter(Mandatory = $false,ParameterSetName = 'Calendars')] [String] $FloorName, [parameter(Mandatory = $false,ParameterSetName = 'Buildings')] [String[]] $FloorNames, [parameter(Mandatory = $false,ParameterSetName = 'Calendars')] [String] $FloorSection, [parameter(Mandatory = $false,ParameterSetName = 'Calendars')] [ValidateSet('CATEGORY_UNKNOWN','CONFERENCE_ROOM','OTHER')] [String] $Category, [parameter(Mandatory = $false,ParameterSetName = 'Calendars')] [String] $ResourceType, [parameter(Mandatory = $false,ParameterSetName = 'Calendars')] [String] $UserVisibleDescription, [parameter(Mandatory = $false)] [ValidateSet('Calendars','Buildings','Features')] [String] $Resource ) Process { $resType = if ($MyInvocation.InvocationName -eq 'Update-GSCalendarResource') { 'Calendars' } elseif ($Resource) { $Resource } else { $PSCmdlet.ParameterSetName } $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.resource.calendar' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams if ($PSBoundParameters -notcontains 'Category' -and $PSCmdlet.ParameterSetName -eq 'Calendars') { $PSBoundParameters['Category'] = 'CATEGORY_UNKNOWN' } try { Write-Verbose "Creating Resource $resType '$Name'" $body = New-Object "$(switch ($resType) { Calendars { 'Google.Apis.Admin.Directory.directory_v1.Data.CalendarResource' } Buildings { 'Google.Apis.Admin.Directory.directory_v1.Data.Building' } Features { 'Google.Apis.Admin.Directory.directory_v1.Data.Feature' } })" foreach ($key in $PSBoundParameters.Keys) { switch ($key) { Category { $body.ResourceCategory = $PSBoundParameters[$key] } Name { if ($resType -eq 'Calendars') { $body.ResourceName = $PSBoundParameters[$key] } elseif ($resType -eq 'Buildings') { $body.BuildingName = $PSBoundParameters[$key] } else { $body.$key = $PSBoundParameters[$key] } } Description { if ($resType -eq 'Calendars') { $body.ResourceDescription = $PSBoundParameters[$key] } else { $body.$key = $PSBoundParameters[$key] } } Default { if ($body.PSObject.Properties.Name -contains $key) { $body.$key = $PSBoundParameters[$key] } } } } $resId = switch ($PSCmdlet.ParameterSetName) { Calendars { $ResourceId } Buildings { $BuildingId } Features { $FeatureKey } } $request = $service.Resources.$resType.Patch($body,$(if ($Script:PSGSuite.CustomerID) { $Script:PSGSuite.CustomerID } else { 'my_customer' }),$resId) $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Resource' -Value $resType -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Update-GSResource' function Get-GSAdminRoleAssignment { <# .SYNOPSIS Gets a specific Admin Role Assignments or the list of Admin Role Assignments for a given role .DESCRIPTION Gets a specific Admin Role Assignments or the list of Admin Role Assignments for a given role .PARAMETER RoleAssignmentId The RoleAssignmentId(s) you would like to retrieve info for. If left blank, returns the full list of Role Assignments .PARAMETER UserKey The UserKey(s) you would like to retrieve Role Assignments for. This can be a user's email or their unique UserId If left blank, returns the full list of Role Assignments .PARAMETER RoleId The RoleId(s) you would like to retrieve Role Assignments for. If left blank, returns the full list of Role Assignments .PARAMETER PageSize Page size of the result set .PARAMETER Limit The maximum amount of results you want returned. Exclude or set to 0 to return all results .EXAMPLE Get-GSAdminRoleAssignment Gets the list of Admin Role Assignments .EXAMPLE Get-GSAdminRoleAssignment -RoleId 9191482342768644,9191482342768642 Gets the Admin Role Assignments matching the provided RoleIds #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.RoleAssignment')] [cmdletbinding(DefaultParameterSetName = "ListUserKey")] Param ( [parameter(Mandatory = $true,Position = 0,ParameterSetName = "Get")] [String[]] $RoleAssignmentId, [parameter(Mandatory = $false,ParameterSetName = "ListUserKey")] [string[]] $UserKey, [parameter(Mandatory = $false,ParameterSetName = "ListRoleId")] [string[]] $RoleId, [parameter(Mandatory = $false,ParameterSetName = "ListUserKey")] [parameter(Mandatory = $false,ParameterSetName = "ListRoleId")] [ValidateRange(1,100)] [Alias("MaxResults")] [Int] $PageSize = 100, [parameter(Mandatory = $false,ParameterSetName = "ListUserKey")] [parameter(Mandatory = $false,ParameterSetName = "ListRoleId")] [Alias('First')] [Int] $Limit = 0 ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.rolemanagement.readonly' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams $customerId = if ($Script:PSGSuite.CustomerID) { $Script:PSGSuite.CustomerID } else { 'my_customer' } } Process { switch ($PSCmdlet.ParameterSetName) { Get { foreach ($Role in $RoleAssignmentId) { try { Write-Verbose "Getting Admin Role Assignment '$Role'" $request = $service.RoleAssignments.Get($customerId,$Role) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Default { [int]$i = 1 $overLimit = $false Write-Verbose "Getting Admin Role Assignment List" $baseRequest = $service.RoleAssignments.List($customerId) 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) $PageSize = $Limit } $baseRequest.MaxResults = $PageSize if ($PSBoundParameters.Keys -contains 'RoleId' -or $PSBoundParameters.Keys -contains 'UserKey') { switch ($PSBoundParameters.Keys) { RoleId { foreach ($Role in $RoleId) { if (-not $overLimit) { try { $request = $baseRequest $request.RoleId = $Role do { $result = $request.Execute() $result.Items | Add-Member -MemberType NoteProperty -Name 'Filter' -Value ([PSCustomObject]@{RoleId = $Role}) -PassThru $request.PageToken = $result.NextPageToken [int]$retrieved = ($i + $result.Items.Count) - 1 Write-Verbose "Retrieved $retrieved role assignments..." if ($Limit -gt 0 -and $retrieved -eq $Limit) { Write-Verbose "Limit reached: $Limit" $overLimit = $true } elseif ($Limit -gt 0 -and ($retrieved + $PageSize) -gt $Limit) { $newPS = $Limit - $retrieved Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize,$newPS) $request.MaxResults = $newPS } [int]$i = $i + $result.Items.Count } until ($overLimit -or !$result.NextPageToken) } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } UserKey { foreach ($User in $UserKey) { try { $request = $baseRequest $uKey = try { [int64]$User } catch { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } (Get-GSUser -User $User -Verbose:$false).Id } $request.UserKey = $uKey do { $result = $request.Execute() $result.Items | Add-Member -MemberType NoteProperty -Name 'Filter' -Value ([PSCustomObject]@{UserKey = $User}) -PassThru $request.PageToken = $result.NextPageToken [int]$retrieved = ($i + $result.Items.Count) - 1 Write-Verbose "Retrieved $retrieved role assignments..." if ($Limit -gt 0 -and $retrieved -eq $Limit) { Write-Verbose "Limit reached: $Limit" $overLimit = $true } elseif ($Limit -gt 0 -and ($retrieved + $PageSize) -gt $Limit) { $newPS = $Limit - $retrieved Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize,$newPS) $request.MaxResults = $newPS } [int]$i = $i + $result.Items.Count } until ($overLimit -or !$result.NextPageToken) } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } } else { try { $request = $baseRequest do { $result = $request.Execute() $result.Items $request.PageToken = $result.NextPageToken [int]$retrieved = ($i + $result.Items.Count) - 1 Write-Verbose "Retrieved $retrieved role assignments..." if ($Limit -gt 0 -and $retrieved -eq $Limit) { Write-Verbose "Limit reached: $Limit" $overLimit = $true } elseif ($Limit -gt 0 -and ($retrieved + $PageSize) -gt $Limit) { $newPS = $Limit - $retrieved Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize,$newPS) $request.MaxResults = $newPS } [int]$i = $i + $result.Items.Count } until ($overLimit -or !$result.NextPageToken) } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } } } Export-ModuleMember -Function 'Get-GSAdminRoleAssignment' function New-GSAdminRoleAssignment { <# .SYNOPSIS Creates a new Admin Role Assignment .DESCRIPTION Creates a new Admin Role Assignment .PARAMETER AssignedTo The unique ID of the user this role is assigned to. .PARAMETER RoleId The ID of the role that is assigned. .PARAMETER OrgUnitId If the role is restricted to an organization unit, this contains the ID for the organization unit the exercise of this role is restricted to. .PARAMETER ScopeType The scope in which this role is assigned. Acceptable values are: * "CUSTOMER" * "ORG_UNIT" .EXAMPLE New-GSAdminRoleAssignment -AssignedTo jsmith -RoleId 9191482342768644 Assign a new role to a given user. #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.RoleAssignment')] [cmdletbinding()] Param ( [parameter(Mandatory = $true, Position = 0)] [String[]] $AssignedTo, [parameter(Mandatory = $true)] [Int64] $RoleId, [parameter(Mandatory = $false)] [String] $OrgUnitId, [parameter(Mandatory = $false)] [ValidateSet('CUSTOMER', 'ORG_UNIT')] [String] $ScopeType = 'CUSTOMER' ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.rolemanagement' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams $customerId = if ($Script:PSGSuite.CustomerID) { $Script:PSGSuite.CustomerID } else { 'my_customer' } } Process { foreach ($Assigned in $AssignedTo) { try { $uKey = try { [int64]$Assigned } catch { if ($Assigned -ceq 'me') { $Assigned = $Script:PSGSuite.AdminEmail } elseif ($Assigned -notlike "*@*.*") { $Assigned = "$($Assigned)@$($Script:PSGSuite.Domain)" } (Get-GSUser -User $Assigned -Verbose:$false).Id } $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.RoleAssignment' $body.ScopeType = $ScopeType foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) { switch ($prop) { AssignedTo { $body.AssignedTo = $uKey } Default { $body.$prop = $PSBoundParameters[$prop] } } } Write-Verbose "Creating Admin Role Assignment for user '$Assigned' for Role Id '$RoleId'" $request = $service.RoleAssignments.Insert($body, $customerId) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'New-GSAdminRoleAssignment' function Remove-GSAdminRoleAssignment { <# .SYNOPSIS Removes a specific Admin Role Assignment or the list of Admin Role Assignments .DESCRIPTION Removes a specific Admin Role Assignment or the list of Admin Role Assignments .PARAMETER RoleAssignmentId The RoleAssignmentId(s) you would like to remove .EXAMPLE Remove-GSAdminRoleAssignment -RoleAssignmentId 9191482342768644,9191482342768642 Removes the role assignments matching the provided Ids #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [String[]] $RoleAssignmentId ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.rolemanagement' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams $customerId = if ($Script:PSGSuite.CustomerID) { $Script:PSGSuite.CustomerID } else { 'my_customer' } } Process { foreach ($Role in $RoleAssignmentId) { try { if ($PSCmdlet.ShouldProcess("Deleting Role Assignment Id '$Role'")) { Write-Verbose "Deleting Role Assignment Id '$Role'" $request = $service.RoleAssignments.Delete($customerId,$Role) $request.Execute() Write-Verbose "Role Assignment Id '$Role' has been successfully deleted" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Remove-GSAdminRoleAssignment' function Get-GSAdminRole { <# .SYNOPSIS Gets a specific Admin Role or the list of Admin Roles .DESCRIPTION Gets a specific Admin Role or the list of Admin Roles .PARAMETER RoleId The RoleId(s) you would like to retrieve info for. If left blank, returns the full list of Roles .PARAMETER PageSize Page size of the result set .PARAMETER Limit The maximum amount of results you want returned. Exclude or set to 0 to return all results .EXAMPLE Get-GSAdminRole Gets the list of Admin Roles .EXAMPLE Get-GSAdminRole -RoleId 9191482342768644,9191482342768642 Gets the admin roles matching the provided Ids #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Role')] [cmdletbinding(DefaultParameterSetName = "List")] Param ( [parameter(Mandatory = $true,Position = 0,ParameterSetName = "Get")] [int64[]] $RoleId, [parameter(Mandatory = $false,ParameterSetName = "List")] [ValidateRange(1,100)] [Alias("MaxResults")] [Int] $PageSize = 100, [parameter(Mandatory = $false,ParameterSetName = "List")] [Alias('First')] [Int] $Limit = 0 ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.rolemanagement' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams $customerId = if ($Script:PSGSuite.CustomerID) { $Script:PSGSuite.CustomerID } else { 'my_customer' } } Process { switch ($PSCmdlet.ParameterSetName) { Get { foreach ($Role in $RoleId) { try { Write-Verbose "Getting Admin Role '$Role'" $request = $service.Roles.Get($customerId,$Role) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } List { try { Write-Verbose "Getting Admin Role List" $request = $service.Roles.List($customerId) 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) $PageSize = $Limit } $request.MaxResults = $PageSize [int]$i = 1 $overLimit = $false do { $result = $request.Execute() $result.Items $request.PageToken = $result.NextPageToken [int]$retrieved = ($i + $result.Items.Count) - 1 Write-Verbose "Retrieved $retrieved roles..." if ($Limit -gt 0 -and $retrieved -eq $Limit) { Write-Verbose "Limit reached: $Limit" $overLimit = $true } elseif ($Limit -gt 0 -and ($retrieved + $PageSize) -gt $Limit) { $newPS = $Limit - $retrieved Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize,$newPS) $request.MaxResults = $newPS } [int]$i = $i + $result.Items.Count } until ($overLimit -or !$result.NextPageToken) } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } } Export-ModuleMember -Function 'Get-GSAdminRole' function New-GSAdminRole { <# .SYNOPSIS Creates a new Admin Role .DESCRIPTION Creates a new Admin Role .PARAMETER RoleName The name of the new role .PARAMETER RolePrivileges The set of privileges that are granted to this role. .PARAMETER RoleDescription A short description of the role. .EXAMPLE Get-GSAdminRole Gets the list of Admin Roles .EXAMPLE Get-GSAdminRole -RoleId '9191482342768644','9191482342768642' Gets the admin roles matching the provided Ids #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Role')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0)] [String] $RoleName, [parameter(Mandatory = $true,Position = 1,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Google.Apis.Admin.Directory.directory_v1.Data.Role+RolePrivilegesData[]] $RolePrivileges, [parameter(Mandatory = $false)] [String] $RoleDescription ) Begin { if ($PSCmdlet.ParameterSetName -eq 'Get') { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.rolemanagement' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } $customerId = if ($Script:PSGSuite.CustomerID) { $Script:PSGSuite.CustomerID } else { 'my_customer' } $privArray = New-Object 'System.Collections.Generic.List[Google.Apis.Admin.Directory.directory_v1.Data.Role+RolePrivilegesData]' } Process { foreach ($Privilege in $RolePrivileges) { $privArray.Add($Privilege) } } End { try { $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.Role' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) { switch ($prop) { RolePrivileges { $body.RolePrivileges = $privArray } Default { $body.$prop = $PSBoundParameters[$prop] } } } Write-Verbose "Creating Admin Role '$RoleName' with the following privileges:`n`t- $($privArray.PrivilegeName -join "`n`t- ")" $request = $service.Roles.Insert($body,$customerId) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'New-GSAdminRole' function Remove-GSAdminRole { <# .SYNOPSIS Removes a specific Admin Role or a list of Admin Roles .DESCRIPTION Removes a specific Admin Role or a list of Admin Roles .PARAMETER RoleId The RoleId(s) you would like to remove .EXAMPLE Remove-GSAdminRole -RoleId 9191482342768644,9191482342768642 Removes the admin roles matching the provided Ids #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [int64[]] $RoleId ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.rolemanagement' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams $customerId = if ($Script:PSGSuite.CustomerID) { $Script:PSGSuite.CustomerID } else { 'my_customer' } } Process { foreach ($Role in $RoleId) { try { if ($PSCmdlet.ShouldProcess("Deleting Role Id '$Role'")) { Write-Verbose "Deleting Role Id '$Role'" $request = $service.Roles.Delete($customerId,$Role) $request.Execute() Write-Verbose "Role Id '$Role' has been successfully deleted" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Remove-GSAdminRole' function Update-GSAdminRole { <# .SYNOPSIS Update an Admin Role .DESCRIPTION Update an Admin Role .PARAMETER RoleId The Id of the role to update .PARAMETER RoleName The name of the role .PARAMETER RolePrivileges The set of privileges that are granted to this role. .PARAMETER RoleDescription A short description of the role. .EXAMPLE Update-GSAdminRole -RoleId 9191482342768644 -RoleName 'Help_Desk_Level2' -RoleDescription 'Help Desk Level 2' Updates the specified Admin Role with a new name and description .EXAMPLE Get-GSAdminRole | Where-Object {$_.RoleDescription -like "*Help*Desk*"} | Update-GSAdminRole -RoleId 9191482342768644 -RoleName 'Help_Desk_Level2' -RoleDescription 'Help Desk Level 2' Updates the specified Admin Role's RolePrivileges to match every other Admin Role with Help Desk in the description. Useful for basing a new role off another to add additional permissions on there #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Role')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0)] [int64] $RoleId, [parameter(Mandatory = $false)] [String] $RoleName, [parameter(Mandatory = $false,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Google.Apis.Admin.Directory.directory_v1.Data.Role+RolePrivilegesData[]] $RolePrivileges, [parameter(Mandatory = $false)] [String] $RoleDescription ) Begin { if ($PSCmdlet.ParameterSetName -eq 'Get') { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.rolemanagement' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } $customerId = if ($Script:PSGSuite.CustomerID) { $Script:PSGSuite.CustomerID } else { 'my_customer' } $privArray = New-Object 'System.Collections.Generic.List[Google.Apis.Admin.Directory.directory_v1.Data.Role+RolePrivilegesData]' } Process { foreach ($Privilege in $RolePrivileges) { $privArray.Add($Privilege) } } End { try { $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.Role' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$_ -ne 'RoleId' -and $body.PSObject.Properties.Name -contains $_}) { switch ($prop) { RolePrivileges { $body.RolePrivileges = $privArray } Default { $body.$prop = $PSBoundParameters[$prop] } } } Write-Verbose "Updating Admin Role '$RoleId'" $request = $service.Roles.Insert($body,$customerId,$RoleId) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Update-GSAdminRole' function Get-GSUserSchema { <# .SYNOPSIS Gets custom user schema info .DESCRIPTION Gets custom user schema info .PARAMETER SchemaId The Id or Name of the user schema you would like to return info for. If excluded, gets the full list of user schemas .EXAMPLE Get-GSUserSchema Gets the list of custom user schemas #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Schema')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias('Schema')] [String[]] $SchemaId ) Begin { if ($PSBoundParameters.Keys -contains 'SchemaId') { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.userschema' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } } Process { try { if ($PSBoundParameters.Keys -contains 'SchemaId') { foreach ($S in $SchemaId) { Write-Verbose "Getting schema Id '$S'" $request = $service.Schemas.Get($Script:PSGSuite.CustomerId,$S) $request.Execute() } } else { Get-GSUserSchemaListPrivate @PSBoundParameters } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSUserSchema' function New-GSUserSchema { <# .SYNOPSIS Creates a new user schema .DESCRIPTION Creates a new user schema .PARAMETER SchemaName The name of the schema to create .PARAMETER Fields New schema fields to set Expects SchemaFieldSpec objects. You can create these with the helper function Add-GSUserSchemaField, i.e.: Add-GSUserSchemaField -FieldName "date" -FieldType DATE -ReadAccessType ADMINS_AND_SELF .EXAMPLE New-GSUserSchema -SchemaName "SDK" -Fields (Add-GSUserSchemaField -FieldName "string" -FieldType STRING -ReadAccessType ADMINS_AND_SELF),(Add-GSUserSchemaField -FieldName "date" -FieldType DATE -ReadAccessType ADMINS_AND_SELF) This command will create a schema named "SDK" with two fields, "string" and "date", readable by ADMINS_AND_SELF #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Schema')] [cmdletbinding()] Param ( [parameter(Mandatory = $true)] [String] $SchemaName, [parameter(Mandatory = $true)] [Google.Apis.Admin.Directory.directory_v1.Data.SchemaFieldSpec[]] $Fields ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.userschema' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { Write-Verbose "Creating schema '$SchemaName'" $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.Schema' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) { switch ($prop) { Default { $body.$prop = $PSBoundParameters[$prop] } } } $request = $service.Schemas.Insert($body,$Script:PSGSuite.CustomerId) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'New-GSUserSchema' function Remove-GSUserSchema { <# .SYNOPSIS Removes a custom user schema .DESCRIPTION Removes a custom user schema .PARAMETER SchemaId The SchemaId or SchemaName to remove. If excluded, all Custom User Schemas for the customer will be removed .EXAMPLE Remove-GSUserSchema 2SV Removes the custom user schema named '2SV' after confirmation #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias('Schema','SchemaKey','SchemaName')] [String[]] $SchemaId ) Begin { if ($PSBoundParameters.Keys -contains 'SchemaName') { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.user.userschema' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } } Process { try { if ($PSBoundParameters.Keys -contains 'SchemaId') { foreach ($S in $SchemaId) { if ($PSCmdlet.ShouldProcess("Deleting User Schema '$S'")) { Write-Verbose "Deleting User Schema '$S'" $request = $service.Schemas.Delete($Script:PSGSuite.CustomerID,$S) $request.Execute() Write-Verbose "User Schema '$S' has been successfully deleted" } } } else { if ($PSCmdlet.ShouldProcess("Deleting ALL User Schemas")) { Write-Verbose "Deleting ALL User Schemas" Get-GSUserSchema -Verbose:$false | ForEach-Object { $request = $service.Schemas.Delete($Script:PSGSuite.CustomerID,$_.SchemaId) $request.Execute() Write-Verbose "User Schema '$($_.SchemaId)' has been successfully deleted" } } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Remove-GSUserSchema' function Set-GSUserSchema { <# .SYNOPSIS Hard-sets a schema's configuration .DESCRIPTION Hard-sets a schema's configuration .PARAMETER SchemaId The unique Id of the schema to set .PARAMETER SchemaName The new schema name .PARAMETER Fields New schema fields to set Expects SchemaFieldSpec objects. You can create these with the helper function Add-GSUserSchemaField, i.e.: Add-GSUserSchemaField -FieldName "date" -FieldType DATE -ReadAccessType ADMINS_AND_SELF .EXAMPLE Set-GSUserSchema -SchemaId "9804800jfl08917304j" -SchemaName "SDK_2" -Fields (Add-GSUserSchemaField -FieldName "string2" -FieldType STRING -ReadAccessType ADMINS_AND_SELF) This command will set the schema Id '9804800jfl08917304j' with the name "SDK_2" and one field "string2" readable by ADMINS_AND_SELF #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Schema')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias('Schema')] [String] $SchemaId, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [String] $SchemaName, [parameter(Mandatory = $false)] [Google.Apis.Admin.Directory.directory_v1.Data.SchemaFieldSpec[]] $Fields ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.userschema' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { Write-Verbose "Setting schema '$SchemaId'" $schemaObj = Get-GSUserSchema -Schema $SchemaId -Verbose:$false $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.Schema' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) { $body.$prop = $PSBoundParameters[$prop] } $request = $service.Schemas.Update($body,$Script:PSGSuite.CustomerId,$schemaObj.SchemaId) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Set-GSUserSchema' function Update-GSUserSchema { <# .SYNOPSIS Updates/patches a schema's configuration .DESCRIPTION Updates/patches a schema's configuration .PARAMETER SchemaId The unique Id of the schema to update .PARAMETER SchemaName The new schema name .PARAMETER Fields New schema fields to set Expects SchemaFieldSpec objects. You can create these with the helper function Add-GSUserSchemaField, i.e.: Add-GSUserSchemaField -FieldName "date" -FieldType DATE -ReadAccessType ADMINS_AND_SELF .EXAMPLE Update-GSUserSchema -SchemaId "9804800jfl08917304j" -SchemaName "SDK_2" -Fields (Add-GSUserSchemaField -FieldName "string2" -FieldType STRING -ReadAccessType ADMINS_AND_SELF) This command will update the schema Id '9804800jfl08917304j' with the name "SDK_2" and add one field "string2" readable by ADMINS_AND_SELF #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Schema')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias('Schema')] [String] $SchemaId, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [String] $SchemaName, [parameter(Mandatory = $false)] [Google.Apis.Admin.Directory.directory_v1.Data.SchemaFieldSpec[]] $Fields ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.userschema' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { Write-Verbose "Updating schema '$SchemaId'" $schemaObj = Get-GSUserSchema -Schema $SchemaId -Verbose:$false $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.Schema' foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) { $body.$prop = $PSBoundParameters[$prop] } $request = $service.Schemas.Patch($body,$Script:PSGSuite.CustomerId,$schemaObj.SchemaId) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Update-GSUserSchema' function Get-GSChromeOSDevice { <# .SYNOPSIS Gets the list of Chrome OS Devices registered for the user's account .DESCRIPTION Gets the list of Chrome OS Devices registered for the user's account .PARAMETER ResourceId Immutable ID of Chrome OS Device. Gets the list of Chrome OS devices if excluded. .PARAMETER OrgUnitPath The full path of the organizational unit or its unique ID. .PARAMETER Filter Search string in the format given at: http://support.google.com/chromeos/a/bin/answer.py?answer=1698333 .PARAMETER Projection Restrict information returned to a set of selected fields. Acceptable values are: * "BASIC": Includes only the basic metadata fields (e.g., deviceId, model, status, type, and status) * "FULL": Includes all metadata fields Defauls to "FULL" .PARAMETER PageSize Page size of the result set .PARAMETER Limit The maximum amount of results you want returned. Exclude or set to 0 to return all results .PARAMETER OrderBy Device property to use for sorting results. Acceptable values are: * "annotatedLocation": Chrome device location as annotated by the administrator. * "annotatedUser": Chrome device user as annotated by the administrator. * "lastSync": The date and time the Chrome device was last synchronized with the policy settings in the Admin console. * "notes": Chrome device notes as annotated by the administrator. * "serialNumber": The Chrome device serial number entered when the device was enabled. * "status": Chrome device status. For more information, see the chromeosdevices resource. * "supportEndDate": Chrome device support end date. This is applicable only for devices purchased directly from Google. .PARAMETER SortOrder Whether to return results in ascending or descending order. Must be used with the OrderBy parameter. Acceptable values are: * "ASCENDING": Ascending order. * "DESCENDING": Descending order. .EXAMPLE Get-GSChromeOSDevice Gets the Chrome OS Device list for the customer #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.ChromeOSDevice')] [cmdletbinding(DefaultParameterSetName = "List")] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = "Get")] [Alias('Id','Device','DeviceId')] [String[]] $ResourceId, [parameter(Mandatory = $false,ParameterSetName = "List")] [Alias('Query')] [String] $Filter, [parameter(Mandatory = $false,ParameterSetName = "List")] [String] $OrgUnitPath, [parameter(Mandatory = $false)] [ValidateSet("BASIC","FULL")] [String] $Projection = "FULL", [parameter(Mandatory = $false,ParameterSetName = "List")] [ValidateRange(1,100)] [Alias('MaxResults')] [Int] $PageSize = 100, [parameter(Mandatory = $false,ParameterSetName = "List")] [Alias('First')] [Int] $Limit = 0, [parameter(Mandatory = $false,ParameterSetName = "List")] [ValidateSet("annotatedLocation","annotatedUser","lastSync","notes","serialNumber","status","supportEndDate")] [String] $OrderBy, [parameter(Mandatory = $false,ParameterSetName = "List")] [ValidateSet("ASCENDING","DESCENDING")] [String] $SortOrder ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.device.chromeos' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams $customerId = if ($Script:PSGSuite.CustomerID) { $Script:PSGSuite.CustomerID } else { "my_customer" } } Process { try { switch ($PSCmdlet.ParameterSetName) { Get { foreach ($dev in $ResourceId) { try { Write-Verbose "Getting Chrome OS Device '$dev'" $request = $service.Chromeosdevices.Get($customerId,$dev) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } List { $request = $service.Chromeosdevices.List($customerId) 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) $PageSize = $Limit } $request.MaxResults = $PageSize if ($PSBoundParameters.Keys -contains 'Filter') { Write-Verbose "Getting Chrome OS Device list for filter '$Filter'" $request.Query = $PSBoundParameters['Filter'] } else { Write-Verbose "Getting Chrome OS Device list" } foreach ($key in $PSBoundParameters.Keys | Where-Object {$_ -notin @('PageSize','Filter')}) { if ($request.PSObject.Properties.Name -contains $key) { $request.$key = $PSBoundParameters[$key] } } [int]$i = 1 $overLimit = $false do { $result = $request.Execute() $result.Chromeosdevices if ($result.NextPageToken) { $request.PageToken = $result.NextPageToken } [int]$retrieved = ($i + $result.Chromeosdevices.Count) - 1 Write-Verbose "Retrieved $retrieved Chrome OS Devices..." if ($Limit -gt 0 -and $retrieved -eq $Limit) { Write-Verbose "Limit reached: $Limit" $overLimit = $true } elseif ($Limit -gt 0 -and ($retrieved + $PageSize) -gt $Limit) { $newPS = $Limit - $retrieved Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize,$newPS) $request.MaxResults = $newPS } [int]$i = $i + $result.Chromeosdevices.Count } until ($overLimit -or !$result.NextPageToken) } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSChromeOSDevice' function Get-GSMobileDevice { <# .SYNOPSIS Gets the list of Mobile Devices registered for the user's account .DESCRIPTION Gets the list of Mobile Devices registered for the user's account .PARAMETER User The user that you would like to retrieve the Mobile Device list for. If no user is specified, it will list all of the Mobile Devices of the CustomerID .PARAMETER Filter Search string in the format given at: http://support.google.com/a/bin/answer.py?hl=en&answer=1408863#search .PARAMETER Projection Restrict information returned to a set of selected fields. Acceptable values are: * "BASIC": Includes only the basic metadata fields (e.g., deviceId, model, status, type, and status) * "FULL": Includes all metadata fields Defauls to "FULL" .PARAMETER PageSize Page size of the result set .PARAMETER Limit The maximum amount of results you want returned. Exclude or set to 0 to return all results .PARAMETER OrderBy Device property to use for sorting results. Acceptable values are: * "deviceId": The serial number for a Google Sync mobile device. For Android devices, this is a software generated unique identifier. * "email": The device owner's email address. * "lastSync": Last policy settings sync date time of the device. * "model": The mobile device's model. * "name": The device owner's user name. * "os": The device's operating system. * "status": The device status. * "type": Type of the device. .PARAMETER SortOrder Whether to return results in ascending or descending order. Must be used with the OrderBy parameter. Acceptable values are: * "ASCENDING": Ascending order. * "DESCENDING": Descending order. .EXAMPLE Get-GSMobileDevice Gets the Mobile Device list for the AdminEmail #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.MobileDevice')] [cmdletbinding(DefaultParameterSetName = "User")] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = "User")] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String[]] $User, [parameter(Mandatory = $false,ParameterSetName = "Query",Position = 0)] [Alias('Query')] [String] $Filter, [parameter(Mandatory = $false)] [ValidateSet("BASIC","FULL")] [String] $Projection = "FULL", [parameter(Mandatory = $false)] [ValidateRange(1,1000)] [Int] $PageSize = 1000, [parameter(Mandatory = $false)] [Alias('First')] [Int] $Limit = 0, [parameter(Mandatory = $false)] [ValidateSet("deviceId","email","lastSync","model","name","os","status","type")] [String] $OrderBy, [parameter(Mandatory = $false)] [ValidateSet("Ascending","Descending")] [String] $SortOrder ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.device.mobile' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams $customerId = if ($Script:PSGSuite.CustomerID) { $Script:PSGSuite.CustomerID } else { "my_customer" } } Process { try { $request = $service.Mobiledevices.List($customerId) 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) $PageSize = $Limit } $request.MaxResults = $PageSize [int]$i = 1 $overLimit = $false switch ($PSCmdlet.ParameterSetName) { User { if ($User) { foreach ($U in $User) { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } $Filter = "email:`"$U`"" $request.Query = $Filter Write-Verbose "Getting Mobile Device list for User '$U'" do { $result = $request.Execute() $result.Mobiledevices if ($result.NextPageToken) { $request.PageToken = $result.NextPageToken } [int]$retrieved = ($i + $result.Mobiledevices.Count) - 1 Write-Verbose "Retrieved $retrieved Mobile Devices..." if ($Limit -gt 0 -and $retrieved -eq $Limit) { Write-Verbose "Limit reached: $Limit" $overLimit = $true } elseif ($Limit -gt 0 -and ($retrieved + $PageSize) -gt $Limit) { $newPS = $Limit - $retrieved Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize,$newPS) $request.MaxResults = $newPS } [int]$i = $i + $result.Mobiledevices.Count } until ($overLimit -or !$result.NextPageToken) } } else { Write-Verbose "Getting Mobile Device list for customer '$($script:PSGSuite.CustomerID)'" do { $result = $request.Execute() $result.Mobiledevices if ($result.NextPageToken) { $request.PageToken = $result.NextPageToken } [int]$retrieved = ($i + $result.Mobiledevices.Count) - 1 Write-Verbose "Retrieved $retrieved Mobile Devices..." if ($Limit -gt 0 -and $retrieved -eq $Limit) { Write-Verbose "Limit reached: $Limit" $overLimit = $true } elseif ($Limit -gt 0 -and ($retrieved + $PageSize) -gt $Limit) { $newPS = $Limit - $retrieved Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize,$newPS) $request.MaxResults = $newPS } [int]$i = $i + $result.Mobiledevices.Count } until ($overLimit -or !$result.NextPageToken) } } Query { $request.Query = $Filter Write-Verbose "Getting Mobile Device list for filter '$Filter'" do { $result = $request.Execute() $result.Mobiledevices if ($result.NextPageToken) { $request.PageToken = $result.NextPageToken } [int]$retrieved = ($i + $result.Mobiledevices.Count) - 1 Write-Verbose "Retrieved $retrieved Mobile Devices..." if ($Limit -gt 0 -and $retrieved -eq $Limit) { Write-Verbose "Limit reached: $Limit" $overLimit = $true } elseif ($Limit -gt 0 -and ($retrieved + $PageSize) -gt $Limit) { $newPS = $Limit - $retrieved Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize,$newPS) $request.MaxResults = $newPS } [int]$i = $i + $result.Mobiledevices.Count } until ($overLimit -or !$result.NextPageToken) } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSMobileDevice' function Get-GSUserASP { <# .SYNOPSIS Gets Application Specific Passwords for a user .DESCRIPTION Gets Application Specific Passwords for a user .PARAMETER User The primary email or UserID of the user who you are trying to get info 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. Defaults to the AdminEmail in the config .PARAMETER CodeId The ID of the ASP you would like info for. If excluded, returns the full list of ASP's for the user .EXAMPLE Get-GSUserASP Gets the list of Application Specific Passwords for the user #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Asp')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String[]] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false,Position = 1,ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [String] $CodeId ) Begin { if ($PSBoundParameters.Keys -contains 'CodeId') { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.user.security' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } } Process { try { foreach ($U in $User) { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } if ($PSBoundParameters.Keys -contains 'CodeId') { Write-Verbose "Getting ASP '$CodeId' for User '$U'" $request = $service.Asps.Get($U,$CodeId) $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru } else { $PSBoundParameters['User'] = $U Get-GSUserASPListPrivate @PSBoundParameters } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSUserASP' function Get-GSUserToken { <# .SYNOPSIS Gets security tokens for a user .DESCRIPTION Gets security tokens for a user .PARAMETER User The primary email or UserID of the user who you are trying to get info 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. Defaults to the AdminEmail in the config .PARAMETER ClientId The Id of the client you are trying to get token info for. If excluded, gets the full list of tokens for the user .EXAMPLE Get-GSUserToken -ClientId "Google Chrome" Gets the token info for "Google Chrome" for the AdminEmail user #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Token')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String[]] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false,Position = 1,ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [String] $ClientId ) Begin { if ($PSBoundParameters.Keys -contains 'ClientId') { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.user.security' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } } Process { try { foreach ($U in $User) { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } if ($PSBoundParameters.Keys -contains 'ClientId') { Write-Verbose "Getting Token '$ClientId' for User '$U'" $request = $service.Tokens.Get($U,$ClientId) $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru } else { $PSBoundParameters['User'] = $U Get-GSUserTokenListPrivate @PSBoundParameters } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSUserToken' function Get-GSUserVerificationCodes { <# .SYNOPSIS Gets the 2-Step Verification Codes for the user .DESCRIPTION Gets the 2-Step Verification Codes for the user .PARAMETER User The primary email or UserID of the user who you are trying to get info 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. Defaults to the AdminEmail in the config .EXAMPLE Get-GSUserVerificationCodes Gets the Verification Codes for AdminEmail user #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.VerificationCode')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String[]] $User = $Script:PSGSuite.AdminEmail ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.user.security' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { foreach ($U in $User) { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } Write-Verbose "Getting Verification Code list for User '$U'" $request = $service.VerificationCodes.List($U) $request.Execute() | Select-Object -ExpandProperty Items | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSUserVerificationCodes' function New-GSUserVerificationCodes { <# .SYNOPSIS Generates new verification codes for the user .DESCRIPTION Generates new verification codes for the user .PARAMETER User The primary email or UserID of the user who you are trying to get info 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 New-GSUserVerificationCodes -User me Generates new verification codes for the AdminEmail user #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.VerificationCode')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String[]] $User ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.user.security' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { foreach ($U in $User) { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } Write-Verbose "Generating new verification codes for user '$U'" $request = $service.VerificationCodes.Generate($U) $request.Execute() Write-Verbose "New verification codes successfully generated for user '$U'" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'New-GSUserVerificationCodes' function Remove-GSMobileDevice { <# .SYNOPSIS Removes a mobile device from Device Management .DESCRIPTION Removes a mobile device from Device Management .PARAMETER ResourceID The unique Id of the mobile device you would like to remove .EXAMPLE Remove-GSMobileDevice -ResourceId 'AFiQxQ8Qgd-rouSmcd2UnuvhYV__WXdacTgJhPEA1QoQJrK1hYbKJXm-8JFlhZOjBF4aVbhleS2FVQk5lI069K2GULpteTlLVpKLJFSLSL' Removes the mobile device with the specified Id #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [String[]] $ResourceId ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.device.mobile' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { foreach ($R in $ResourceId) { if ($PSCmdlet.ShouldProcess("Removing Mobile Device '$R'")) { Write-Verbose "Removing Mobile Device '$R'" $request = $service.Mobiledevices.Delete($Script:PSGSuite.CustomerID,$R) $request.Execute() Write-Verbose "Mobile Device '$R' has been successfully removed" } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Remove-GSMobileDevice' function Remove-GSUserASP { <# .SYNOPSIS Removes an Application Specific Password for a user .DESCRIPTION Removes an Application Specific Password for a user .PARAMETER User The user to remove ASPs ValueFromPipeline .PARAMETER CodeId The ASP Code Id to remove. If excluded, all ASPs for the user will be removed .EXAMPLE Remove-GSUserASP -User joe Removes *ALL* ASPs from joe@domain.com's account after confirmation #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String[]] $User, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [String[]] $CodeId ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.user.security' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { foreach ($U in $User) { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } if ($PSBoundParameters.Keys -contains 'CodeId') { foreach ($C in $CodeId) { if ($PSCmdlet.ShouldProcess("Deleting ASP CodeId '$C' for user '$U'")) { Write-Verbose "Deleting ASP CodeId '$C' for user '$U'" $request = $service.Asps.Delete($U,$C) $request.Execute() Write-Verbose "ASP CodeId '$C' has been successfully deleted for user '$U'" } } } else { if ($PSCmdlet.ShouldProcess("Deleting ALL ASPs for user '$U'")) { Write-Verbose "Deleting ALL ASPs for user '$U'" Get-GSUserASP -User $U -Verbose:$false | ForEach-Object { $request = $service.Asps.Delete($U,$_.CodeId) $request.Execute() Write-Verbose "ASP CodeId '$($_.CodeId)' has been successfully deleted for user '$U'" } } } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Remove-GSUserASP' function Remove-GSUserToken { <# .SYNOPSIS Removes a security token from a user .DESCRIPTION Removes a security token from a user .PARAMETER User The user to remove the security token from .PARAMETER ClientID The client Id of the security token. If excluded, all security tokens for the user are removed .EXAMPLE An example .NOTES General notes #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String[]] $User, [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [String[]] $ClientID ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.user.security' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { foreach ($U in $User) { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } if ($PSBoundParameters.Keys -contains 'ClientID') { foreach ($C in $ClientID) { if ($PSCmdlet.ShouldProcess("Deleting Token ClientID '$C' for user '$U'")) { Write-Verbose "Deleting Token ClientID '$C' for user '$U'" $request = $service.Tokens.Delete($U,$C) $request.Execute() Write-Verbose "Token ClientID '$C' has been successfully deleted for user '$U'" } } } else { if ($PSCmdlet.ShouldProcess("Deleting ALL tokens for user '$U'")) { Write-Verbose "Deleting ALL tokens for user '$U'" Get-GSUserToken -User $U -Verbose:$false | ForEach-Object { $request = $service.Tokens.Delete($U,$_.ClientID) $request.Execute() Write-Verbose "Token ClientID '$($_.ClientID)' has been successfully deleted for user '$U'" } } } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Remove-GSUserToken' function Revoke-GSUserVerificationCodes { <# .SYNOPSIS Revokes/invalidates Verification Codes for the user .DESCRIPTION Revokes/invalidates Verification Codes for the user .PARAMETER User The user to revoke verification codes from .EXAMPLE Revoke-GSUserVerificationCodes -User me -Confirm:$false Invalidates the verification codes for the AdminEmail user, skipping confirmation #> [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String[]] $User ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.user.security' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { foreach ($U in $User) { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } Write-Verbose "Invalidating verification codes for user '$U'" $request = $service.VerificationCodes.Invalidate($U) $request.Execute() Write-Verbose "Verification codes successfully invalidated for user '$U'" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Revoke-GSUserVerificationCodes' function Update-GSChromeOSDevice { <# .SYNOPSIS Updates a ChromeOS device .DESCRIPTION Updates a ChromeOS device .PARAMETER ResourceId The unique ID of the device you would like to update. .PARAMETER Action Action to be taken on the Chrome OS device. Acceptable values are: * "deprovision": Remove a device from management that is no longer active, being resold, or is being submitted for return / repair, use the deprovision action to dissociate it from management. * "disable": If you believe a device in your organization has been lost or stolen, you can disable the device so that no one else can use it. When a device is disabled, all the user can see when turning on the Chrome device is a screen telling them that it’s been disabled, and your desired contact information of where to return the device. Note: Configuration of the message to appear on a disabled device must be completed within the admin console. "reenable": Re-enable a disabled device when a misplaced device is found or a lost device is returned. You can also use this feature if you accidentally mark a Chrome device as disabled. Note: The re-enable action can only be performed on devices marked as disabled. .PARAMETER DeprovisionReason Only used when the action is deprovision. With the deprovision action, this field is required. Note: The deprovision reason is audited because it might have implications on licenses for perpetual subscription customers. Acceptable values are: * "different_model_replacement": Use if you're upgrading or replacing your device with a newer model of the same device. * "retiring_device": Use if you're reselling, donating, or permanently removing the device from use. * "same_model_replacement": Use if a hardware issue was encountered on a device and it is being replaced with the same model or a like-model replacement from a repair vendor / manufacturer. .PARAMETER AnnotatedAssetId The asset identifier as noted by an administrator or specified during enrollment. .PARAMETER AnnotatedLocation The address or location of the device as noted by the administrator. Maximum length is 200 characters. Empty values are allowed. .PARAMETER AnnotatedUser The user of the device as noted by the administrator. Maximum length is 100 characters. Empty values are allowed. .PARAMETER Notes Notes about this device added by the administrator. This property can be searched with the list method's query parameter. Maximum length is 500 characters. Empty values are allowed. .PARAMETER OrgUnitPath Full path of the target organizational unit or its ID that you would like to move the device to. .EXAMPLE Update-GSChromeOSDevice -ResourceId 'AFiQxQ8Qgd-rouSmcd2UnuvhYV__WXdacTgJhPEA1QoQJrK1hYbKJXm-8JFlhZOjBF4aVbhleS2FVQk5lI069K2GULpteTlLVpKLJFSLSL' -Action deprovision -DeprovisionReason retiring_device Deprovisions the retired ChromeOS device #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.ChromeOSDevice')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias('Id','Device','DeviceId')] [String[]] $ResourceId, [parameter(Mandatory = $false)] [ValidateSet('deprovision','disable','reenable')] [String] $Action, [parameter(Mandatory = $false)] [ValidateSet('different_model_replacement','retiring_device','same_model_replacement')] [String] $DeprovisionReason, [parameter(Mandatory = $false)] [String] $AnnotatedAssetId, [parameter(Mandatory = $false)] [AllowNull()] [String] $AnnotatedLocation, [parameter(Mandatory = $false)] [AllowNull()] [String] $AnnotatedUser, [parameter(Mandatory = $false)] [AllowNull()] [String] $Notes, [parameter(Mandatory = $false)] [String] $OrgUnitPath ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.device.chromeos' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams $customerId = if ($Script:PSGSuite.CustomerID) { $Script:PSGSuite.CustomerID } else { "my_customer" } $bodyUpdated = $false } Process { $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.ChromeOsDevice' foreach ($key in $PSBoundParameters.Keys | Where-Object {$_ -in @('Action','OrgUnitPath') -or $_ -in $body.PSObject.Properties.Name}) { switch ($key) { Action { try { $actionBody = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.ChromeOSDeviceAction' -Property @{ Action = $Action } if ($PSBoundParameters.Keys -contains 'DeprovisionReason') { $actionBody.DeprovisionReason = $PSBoundParameters['DeprovisionReason'] } foreach ($dev in $ResourceId) { Write-Verbose "Updating Chrome OS Device '$dev' with Action '$Action'" $request = $service.Chromeosdevices.Action($actionBody,$customerId,$dev) $request.Execute() Write-Verbose "Chrome OS Device was successfully updated" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } OrgUnitPath { try { Move-GSChromeOSDevice -ResourceId $ResourceId -OrgUnitPath $OrgUnitPath } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } default { if ($key -in $body.PSObject.Properties.Name) { $bodyUpdated = $true $body.$key = $PSBoundParameters[$key] } } } } if ($bodyUpdated) { foreach ($dev in $ResourceId) { try { Write-Verbose "Updating Chrome OS Device '$dev'" $request = $service.Chromeosdevices.Patch($body,$customerId,$dev) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } } Export-ModuleMember -Function 'Update-GSChromeOSDevice' function Update-GSMobileDevice { <# .SYNOPSIS Updates a mobile device with the action specified .DESCRIPTION Updates a mobile device with the action specified .PARAMETER ResourceID The unique Id of the mobile device you would like to update .PARAMETER Action The action to be performed on the device. Acceptable values are: * "admin_account_wipe": Remotely wipes only G Suite data from the device. See the administration help center for more information. * "admin_remote_wipe": Remotely wipes all data on the device. See the administration help center for more information. * "approve": Approves the device. If you've selected Enable device activation, devices that register after the device activation setting is enabled will need to be approved before they can start syncing with your domain. Enabling device activation forces the device user to install the Device Policy app to sync with G Suite. * "block": Blocks access to G Suite data (mail, calendar, and contacts) on the device. The user can still access their mail, calendar, and contacts from a desktop computer or mobile browser. * "cancel_remote_wipe_then_activate": Cancels a remote wipe of the device and then reactivates it. * "cancel_remote_wipe_then_block": Cancels a remote wipe of the device and then blocks it. .EXAMPLE Update-GSMobileDevice -ResourceId 'AFiQxQ8Qgd-rouSmcd2UnuvhYV__WXdacTgJhPEA1QoQJrK1hYbKJXm-8JFlhZOjBF4aVbhleS2FVQk5lI069K2GULpteTlLVpKLJFSLSL' -Action approve Approves the mobile device with the specified Id #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.MobileDevice')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [String[]] $ResourceId, [parameter(Mandatory = $true)] [ValidateSet('admin_account_wipe','admin_remote_wipe','approve','block','cancel_remote_wipe_then_activate','cancel_remote_wipe_then_block')] [String] $Action ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.device.mobile' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams $customerId = if ($Script:PSGSuite.CustomerID) { $Script:PSGSuite.CustomerID } else { "my_customer" } $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.MobileDeviceAction' -Property @{ Action = $Action } } Process { try { foreach ($R in $ResourceId) { Write-Verbose "Updating Mobile Device '$R' with Action '$Action'" $request = $service.Mobiledevices.Action($body,$customerId,$R) $request.Execute() Write-Verbose "Mobile Device was successfully updated" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Update-GSMobileDevice' 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: * "INPUTVALUEOPTIONUNSPECIFIED" * "RAW" * "USERENTERED" .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 $_ } } } } Export-ModuleMember -Function 'Add-GSSheetValues' function Clear-GSSheet { <# .SYNOPSIS Clears a Sheet .DESCRIPTION Clears a Sheet .PARAMETER SpreadsheetId The unique Id of the SpreadSheet .PARAMETER SheetName The name of the Sheet (tab) to clear .PARAMETER Range The specific range to clear. If excluded, clears the entire Sheet .PARAMETER User The primary email of the user who has Edit rights to the target Range/Sheet .PARAMETER Raw If $true, return the raw response, otherwise, return a flattened response for readability .EXAMPLE Clear-GSSheet -SpreadsheetId '1ZVdewVhy-VtVLyGL1lk2kgvySIF_bCfJA6ggn7obGh2U' -SheetName 2017 Clears the Sheet '2017' located on the SpreadSheet Id provided #> [OutputType('Google.Apis.Sheets.v4.Data.Spreadsheet')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0)] [String] $SpreadsheetId, [parameter(Mandatory = $false)] [String] $SheetName, [parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [Alias('SpecifyRange')] [string] $Range, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false)] [switch] $Raw ) 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 ($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.ClearValuesRequest' $request = $service.Spreadsheets.Values.Clear($body,$SpreadsheetId,$Range) Write-Verbose "Clearing range '$Range' on Sheet '$SpreadsheetId' for user '$User'" $response = $request.Execute() if (!$Raw) { $response = $response | Select-Object @{N = "Title";E = {$_.properties.title}},@{N = "MaxRows";E = {[int]($_.sheets.properties.gridProperties.rowCount | Sort-Object | Select-Object -Last 1)}},@{N = "MaxColumns";E = {[int]($_.sheets.properties.gridProperties.columnCount | Sort-Object | Select-Object -Last 1)}},* } $response | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Clear-GSSheet' function Copy-GSSheet { <# .SYNOPSIS Copies a Sheet from one SpreadSheet to another .DESCRIPTION Copies a Sheet from one SpreadSheet to another .PARAMETER SourceSpreadsheetId The unique Id of the SpreadSheet to copy the Sheet from .PARAMETER SourceSheetId The Id of the Sheet to copy .PARAMETER DestinationSpreadsheetId The target SpreadSheet to copy the Sheet to .PARAMETER NewSheetTitle The new title for the new SpreadhSheet to create if not copying to a Destination Sheet .PARAMETER User The primary email of the user who has at least Edit rights to both the Source SpreadSheet and Destination SpreadSheet .PARAMETER Raw If $true, return the raw response, otherwise, return a flattened response for readability .EXAMPLE Copy-GSSheet -SourceSpreadsheetId '1ZVdewVhy-VtVLyGLhClkj8234ljk_fJA6ggn7obGh2U' -SourceSheetId 2017 -NewSheetTitle '2017 Archive' Copies the Sheet '2017' from the SourceSpreadsheet provided onto a new SpreadSheet named '2017 Archive' #> [cmdletbinding(DefaultParameterSetName = "CreateNewSheet")] Param ( [parameter(Mandatory = $true,Position = 0)] [String] $SourceSpreadsheetId, [parameter(Mandatory = $true,Position = 1)] [String] $SourceSheetId, [parameter(Mandatory = $true,Position = 2,ParameterSetName = "UseExisting")] [String] $DestinationSpreadsheetId, [parameter(Mandatory = $false,ParameterSetName = "CreateNewSheet")] [Alias('SheetTitle')] [String] $NewSheetTitle, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false)] [switch] $Raw ) 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 ($PSCmdlet.ParameterSetName -eq "CreateNewSheet") { if ($NewSheetTitle) { Write-Verbose "Creating new spreadsheet titled: $NewSheetTitle" } else { Write-Verbose "Creating new untitled spreadsheet" } $DestinationSpreadsheetId = New-GSSheet -Title $NewSheetTitle -User $User -Verbose:$false | Select-Object -ExpandProperty SpreadsheetId Write-Verbose "New spreadsheet ID: $DestinationSpreadsheetId" } $body = New-Object 'Google.Apis.Sheets.v4.Data.CopySheetToAnotherSpreadsheetRequest' -Property @{ DestinationSpreadsheetId = $DestinationSpreadsheetId } Write-Verbose "Copying Sheet '$SourceSheetId' from Spreadsheet '$SourceSpreadsheetId' to Spreadsheet '$DestinationSpreadsheetId' for user '$User'" $request = $service.Spreadsheets.Sheets.CopyTo($body,$SourceSpreadsheetId,$SourceSheetId) $response = $request.Execute() if (!$Raw) { $response = $response | Select-Object @{N = "Title";E = {$_.properties.title}},@{N = "MaxRows";E = {[int]($_.sheets.properties.gridProperties.rowCount | Sort-Object | Select-Object -Last 1)}},@{N = "MaxColumns";E = {[int]($_.sheets.properties.gridProperties.columnCount | Sort-Object | Select-Object -Last 1)}},* } $response | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Copy-GSSheet' function Export-GSSheet { <# .SYNOPSIS Updates a Sheet's values .DESCRIPTION Updates a Sheet's values. Accepts either an Array of objects/strings/ints or a single value .PARAMETER SpreadsheetId The unique Id of the SpreadSheet to update if updating an existing Sheet .PARAMETER NewSheetTitle The title of the new SpreadSheet to be created .PARAMETER Array Array of objects/strings/ints to add to the SpreadSheet .PARAMETER Value A single value to update 1 cell with. Useful if you are tracking the last time updated in a specific cell during a job that updates Sheets .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 specific range to add the value(s) to. If using the -Value parameter, set this to the specific cell you would like to set the value of .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 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 $array | Export-GSSheet -NewSheetTitle "Finance Workbook" -Launch #> [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)] [ValidateSet("INPUT_VALUE_OPTION_UNSPECIFIED","RAW","USER_ENTERED")] [string] $ValueInputOption = "RAW", [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 -IncludeGridData:$false -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" } } $bodyData = (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 }) $body = New-Object 'Google.Apis.Sheets.v4.Data.BatchUpdateValuesRequest' $body.ValueInputOption = $ValueInputOption $body.IncludeValuesInResponse = $IncludeValuesInResponse $body.Data = [Google.Apis.Sheets.v4.Data.ValueRange[]]$bodyData $request = $service.Spreadsheets.Values.BatchUpdate($body,$SpreadsheetId) Write-Verbose "Updating 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 $_ } } } } Export-ModuleMember -Function 'Export-GSSheet' function Get-GSSheetInfo { <# .SYNOPSIS Gets metadata about a SpreadSheet .DESCRIPTION Gets metadata about a SpreadSheet .PARAMETER SpreadsheetId The unique Id of the SpreadSheet to retrieve info for .PARAMETER User The owner of the SpreadSheet .PARAMETER SheetName The name of the Sheet to retrieve info for .PARAMETER Range The specific range of the Sheet to retrieve info for .PARAMETER IncludeGridData Whether or not to include Grid Data in the response. Defaults to $false .PARAMETER Fields The fields to return in the response Available values are: * "namedRanges" * "properties" * "sheets" * "spreadsheetId" .PARAMETER Raw If $true, return the raw response, otherwise, return a flattened response for readability .EXAMPLE Get-GSSheetInfo -SpreadsheetId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976' Gets the info for the SpreadSheet provided #> [OutputType('Google.Apis.Sheets.v4.Data.Spreadsheet')] [cmdletbinding()] Param ( [parameter(Mandatory = $true)] [String] $SpreadsheetId, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false)] [String] $SheetName, [parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [Alias('SpecifyRange')] [string] $Range, [parameter(Mandatory = $false)] [Switch] $IncludeGridData, [parameter(Mandatory = $false)] [ValidateSet("namedRanges","properties","sheets","spreadsheetId")] [string[]] $Fields, [parameter(Mandatory = $false)] [switch] $Raw ) 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 ($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" } } $request = $service.Spreadsheets.Get($SpreadsheetId) if ($Range) { $request.Ranges = [Google.Apis.Util.Repeatable[String]]::new([String[]]$Range) } if ($Fields) { $request.Fields = "$(($Fields | ForEach-Object {$f = $_;@("namedRanges","properties","sheets","spreadsheetId") | Where-Object {$_ -eq $f}}) -join ",")" } if ($PSBoundParameters.Keys -contains 'IncludeGridData') { $request.IncludeGridData = $IncludeGridData } Write-Verbose "Getting Spreadsheet Id '$SpreadsheetId' for user '$User'" $response = $request.Execute() if (!$Raw) { $response = $response | Select-Object @{N = "Title";E = {$_.properties.title}},@{N = "MaxRows";E = {[int]($_.sheets.properties.gridProperties.rowCount | Sort-Object | Select-Object -Last 1)}},@{N = "MaxColumns";E = {[int]($_.sheets.properties.gridProperties.columnCount | Sort-Object | Select-Object -Last 1)}},* } $response | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSSheetInfo' function Import-GSSheet { <# .SYNOPSIS Imports data from a Sheet as if it was a CSV .DESCRIPTION Imports data from a Sheet as if it was a CSV .PARAMETER SpreadsheetId The unique Id of the SpreadSheet to import data from .PARAMETER SheetName The name of the Sheet to import data from .PARAMETER User The owner of the SpreadSheet .PARAMETER Range The specific range to import data from .PARAMETER RowStart The starting row of data. Useful if the headers for your table are not in Row 1 of the Sheet .PARAMETER Headers Allows you to define the headers for the rows on the sheet, in case there is no header row .PARAMETER DateTimeRenderOption How to render the DateTime cells Available values are: * "FORMATTED_STRING" (Default) * "SERIAL_NUMBER" .PARAMETER ValueRenderOption How to render the value cells and formula cells Available values are: * "FORMATTED_VALUE" (Default) * "UNFORMATTED_VALUE" * "FORMULA" .PARAMETER MajorDimension The major dimension that results should use. For example, if the spreadsheet data is: A1=1,B1=2,A2=3,B2=4, then requesting range=A1:B2,majorDimension=ROWS will return [[1,2],[3,4]], whereas requesting range=A1:B2,majorDimension=COLUMNS will return [[1,3],[2,4]]. Available values are: * "ROWS" (Default) * "COLUMNS" * "DIMENSION_UNSPECIFIED" .PARAMETER As Whether to return the result set as an array of PSObjects or an array of DataRows Available values are: * "PSObject" (Default) * "DataRow" .PARAMETER Raw If $true, return the raw response, otherwise, return a flattened response for readability .EXAMPLE Import-GSSheet -SpreadsheetId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976' -SheetName Sheet1 -RowStart 2 -Range 'B:C' Imports columns B-C as an Array of PSObjects, skipping the first row and treating Row 2 as the header row. Objects in the array will be what's contained in range 'B3:C' after that #> [cmdletbinding(DefaultParameterSetName = "Import")] Param ( [parameter(Mandatory = $true)] [String] $SpreadsheetId, [parameter(Mandatory = $false)] [String] $SheetName, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [Alias('SpecifyRange')] [string] $Range, [parameter(Mandatory = $false,ParameterSetName = "Import")] [int] $RowStart = 1, [parameter(Mandatory = $false,ParameterSetName = "Import")] [string[]] $Headers, [parameter(Mandatory = $false)] [ValidateSet("FORMATTED_STRING","SERIAL_NUMBER")] [string] $DateTimeRenderOption = "FORMATTED_STRING", [parameter(Mandatory = $false)] [ValidateSet("FORMATTED_VALUE","UNFORMATTED_VALUE","FORMULA")] [string] $ValueRenderOption = "FORMATTED_VALUE", [parameter(Mandatory = $false)] [ValidateSet("ROWS","COLUMNS","DIMENSION_UNSPECIFIED")] [string] $MajorDimension = "ROWS", [Parameter(Mandatory = $false,ParameterSetName = "Import")] [ValidateSet("DataRow","PSObject")] [string] $As = "PSObject", [parameter(Mandatory = $false,ParameterSetName = "Raw")] [switch] $Raw ) 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 ($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" } } $request = $service.Spreadsheets.Values.BatchGet($SpreadsheetId) $request.Ranges = [Google.Apis.Util.Repeatable[String]]::new([String[]]$Range) $request.DateTimeRenderOption = [Google.Apis.Sheets.v4.SpreadsheetsResource+ValuesResource+GetRequest+DateTimeRenderOptionEnum]::$($DateTimeRenderOption -replace "_","") $request.ValueRenderOption = [Google.Apis.Sheets.v4.SpreadsheetsResource+ValuesResource+GetRequest+ValueRenderOptionEnum]::$($ValueRenderOption -replace "_","") $request.MajorDimension = [Google.Apis.Sheets.v4.SpreadsheetsResource+ValuesResource+GetRequest+MajorDimensionEnum]::$($MajorDimension -replace "_","") if ($MajorDimension -ne "ROWS" -and !$Raw) { $Raw = $true Write-Warning "Setting -Raw to True -- Parsing requires the MajorDimension to be set to ROWS (default value)" } Write-Verbose "Importing Range '$Range' from Spreadsheet '$SpreadsheetId' for user '$User'" $response = $request.Execute() if (!$Raw) { $i = 0 $datatable = New-Object System.Data.Datatable if ($Headers) { foreach ($col in $Headers) { [void]$datatable.Columns.Add("$col") } $i++ } $(if ($RowStart) { $response.valueRanges.values | Select-Object -Skip $([int]$RowStart - 1) } else { $response.valueRanges.values }) | ForEach-Object { if ($i -eq 0) { foreach ($col in $_) { [void]$datatable.Columns.Add("$col") } } else { [void]$datatable.Rows.Add([String[]]$_[0..($datatable.Columns.Count - 1)]) } $i++ } switch ($As) { DataRow { Write-Verbose "Created DataTable with $($i - 1) DataRows" $datatable } PSObject { Write-Verbose "Created PSObject array with $($i - 1) objects" foreach ($row in $datatable) { $obj = [Ordered]@{} $props = $row.Table.Columns.ColumnName foreach ($prop in $props) { $obj[$prop] = $row.$prop } [PSCustomObject]$obj } } } } else { $response | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Import-GSSheet' function New-GSSheet { <# .SYNOPSIS Creates a new SpreadSheet .DESCRIPTION Creates a new SpreadSheet .PARAMETER Title The name of the new SpreadSheet .PARAMETER User The user to create the Sheet for .PARAMETER Launch If $true, opens the new SpreadSheet Url in your default browser .EXAMPLE New-GSSheet -Title "Finance Workbook" -Launch Creates a new SpreadSheet titled "Finance Workbook" and opens it in the browser on creation #> [OutputType('Google.Apis.Sheets.v4.Data.Spreadsheet')] [cmdletbinding()] Param ( [parameter(Mandatory = $false)] [Alias('SheetTitle')] [String] $Title, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false)] [Alias('Open')] [Switch] $Launch ) 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 { $body = New-Object 'Google.Apis.Sheets.v4.Data.Spreadsheet' $body.Properties = New-Object 'Google.Apis.Sheets.v4.Data.SpreadsheetProperties' -Property @{ Title = $Title } if (!$Title) { $Title = "Untitled spreadsheet" } Write-Verbose "Creating Spreadsheet '$Title' for user '$User'" $request = $service.Spreadsheets.Create($body) $response = $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru if ($Launch) { Write-Verbose "Launching new spreadsheet at $($response.SpreadsheetUrl)" Start-Process $response.SpreadsheetUrl } $response } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'New-GSSheet' function Submit-GSSheetBatchUpdateRequest { <# .SYNOPSIS Submits a batch update request to a Google Sheet .DESCRIPTION Submits a batch update request to a Google Sheet .PARAMETER Title The name of the SpreadSheet .PARAMETER User The user to update the Sheet for .PARAMETER Launch If $true, opens the SpreadSheet Url in your default browser .EXAMPLE Update-GSSheet -Title "Finance Workbook" -Launch Creates a new SpreadSheet titled "Finance Workbook" and opens it in the browser on creation #> [OutputType('Google.Apis.Sheets.v4.Data.Spreadsheet')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] [Alias('SheetId')] [String] $Id, [parameter(Mandatory = $false)] [Alias('SheetTitle')] [String] $Title, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [Alias('Owner','PrimaryEmail','UserKey','Mail')] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false)] [Alias('Open')] [Switch] $Launch ) 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 { $body = New-Object 'Google.Apis.Sheets.v4.Data.Spreadsheet' $body.Properties = New-Object 'Google.Apis.Sheets.v4.Data.SpreadsheetProperties' -Property @{ Title = $Title } if (!$Title) { $Title = "Untitled spreadsheet" } Write-Verbose "Creating Spreadsheet '$Title' for user '$User'" $request = $service.Spreadsheets.Create($body) $response = $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru if ($Launch) { Write-Verbose "Launching new spreadsheet at $($response.SpreadsheetUrl)" Start-Process $response.SpreadsheetUrl } $response } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Update-GSSheet' function Edit-GSPresentation { <# .SYNOPSIS Updates a Presentation .DESCRIPTION Updates a Presentation. Accepts update requests created by New-GSPresentationUpdateRequest .PARAMETER PresentationId The uniqe Id of the Presentation .PARAMETER Update The update requests, as created by New-GSPresentationUpdateRequest. Can either be passed as a list of updates, or updates can piped into the command .PARAMETER User The primary email of the user that had at least Edit rights to the target Sheet Defaults to the AdminEmail user .PARAMETER Launch If $true, opens the Presentation Url in your default browser .INPUTS Google.Apis.Slides.v1.Data.Request, created by New-GSPresentationUpdateRequest .OUTPUTS Google.Apis.Slides.v1.Data.BatchUpdatePresentationResponse Information about the response is availabe here: https://developers.google.com/slides/reference/rest/v1/presentations/batchUpdate#response-body The Replies Property will be a List of Google.Apis.Slides.v1.Data.Response, in the same order as the submitted requests .EXAMPLE $newSlide = New-GSSlideUpdateRequest -RequestType CreateSlide -RequestProperties @{} $newSlide | Edit-GSPresentation -PresentationID $ID #Will add one new slide at the end of the Presentation Edit-GSPresentation -PresentationID $ID -Update $newSlide,$newSlide #Will execute the newslide request twice, adding two new slides to the end of the Presentation .NOTES Executing multiple updates at once ensures the updates happen atomically, as one action. If there is a problem with one of the updates, none of them will be executed. If you wanted updates to happen individually, you would need to call this command multiple times, once per Update. #> [OutputType('Google.Apis.Slides.v1.Data.BatchUpdatePresentationResponse')] [cmdletbinding()] Param ( [parameter(Mandatory = $true, Position = 0)] [String] $PresentationId, [parameter(Mandatory = $true, Position = 1, ValueFromPipeline = $true)] [System.Collections.Generic.List[Google.Apis.Slides.v1.Data.Request]] $Update, [parameter(Mandatory = $false)] [Alias('Owner', 'PrimaryEmail', 'UserKey', 'Mail')] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false)] [Alias('Open')] [Switch] $Launch ) Begin { 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.Slides.v1.SlidesService' User = $User } $service = New-GoogleService @serviceParams [System.Collections.Generic.List[Google.Apis.Slides.v1.Data.Request]]$requests = @() $body = New-Object 'Google.Apis.Slides.v1.Data.BatchUpdatePresentationRequest' -Property @{Requests = $requests} $presentation = Get-GSDriveFile -FileId $PresentationId $PresentationUrl = $presentation.WebViewLink } Process { foreach ($item in $Update) { $requestType = ($item.psobject.Properties | Where-Object {$_.Value}).Name Write-Verbose "Adding Request of type $requestType to Request Body" $body.Requests.Add($item) } } End { try { $request = $service.Presentations.BatchUpdate($body, $PresentationId) Write-Verbose "Updating Presentation '$PresentationId' for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru | Add-Member -MemberType NoteProperty -Name 'PresentationUrl' -Value $PresentationUrl -PassThru if ($Launch) { Write-Verbose "Launching Presentation at $PresentationUrl" Start-Process $PresentationUrl } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Edit-GSPresentation' function Get-GSPresentation { <# .SYNOPSIS Retrieves a Presentation .DESCRIPTION Retrieves a Presentation, in the form of a Google.Apis.Slides.v1.Data.Presentation object representing the presentation .PARAMETER PresentationId The unique Id of the Presentation .PARAMETER User The primary email of the user that has at least View rights to the target Presentation Defaults to the AdminEmail user .PARAMETER Launch If $true, opens the Presentation Url in your default browser .OUTPUTS Google.Apis.Slides.v1.Data.Presentation An overview of the properties of a Presentation are available here: https://developers.google.com/slides/reference/rest/v1/presentations .EXAMPLE $presentation = Get-GSPresentation -PresentationID $ID #> [OutputType('Google.Apis.Slides.v1.Data.Presentation')] [CmdletBinding()] param ( [parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)] [String] $PresentationId, [parameter(Mandatory = $false)] [Alias('Owner', 'PrimaryEmail', 'UserKey', 'Mail')] [string] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false)] [Alias('Open')] [Switch] $Launch ) begin { 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.Slides.v1.SlidesService' User = $User } $service = New-GoogleService @serviceParams } process { try { $request = $service.Presentations.Get($PresentationId) Write-Verbose "Getting Presentation '$PresentationId' for user '$User'" $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru | Add-Member -MemberType NoteProperty -Name 'PresentationUrl' -Value $PresentationUrl -PassThru if ($Launch) { $presentation = Get-GSDriveFile -FileId $PresentationId $PresentationUrl = $presentation.WebViewLink Write-Verbose "Launching Presentation at $PresentationUrl" Start-Process $PresentationUrl } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } end { } } Export-ModuleMember -Function 'Get-GSPresentation' function New-GSPresentationUpdateRequest { <# .SYNOPSIS Creates an Update Request to be used with Edit-GSPresentation .DESCRIPTION Creates an Update Request to be used with Edit-GSPresentation. Does the hard work of creating a "Google.Apis.Slides.v1.Data.$($RequestType)Request" and generating a 'Google.Apis.Slides.v1.Data.Request' from it. .PARAMETER RequestType The type of update request, as described here: https://developers.google.com/slides/reference/rest/v1/presentations/request Will dynamically validate RequestType based on request types supported by the Slides API library .PARAMETER RequestProperties The properties for the specified RequestType, as described here: https://developers.google.com/slides/reference/rest/v1/presentations/request These properties must be strictly formatted to exactly match the parameters for the given request. .OUTPUTS Google.Apis.Slides.v1.Data.Request .EXAMPLE $newSlide = New-GSSlideUpdateRequest -RequestType CreateSlide -RequestProperties @{} This will create a request to create a new slide at the end of the document with a uniquely generatd ObjectId. The slide will be created with no layout. $moveSlideProperties = @{slideObjectIds = @($slideToMove.ObjectId); insertionIndex=0} $moveSlide = New-GSPresentationUpdateRequest -RequestType UpdateSlidesPosition -RequestProperties $moveSlideProperties This will move the slide specified by $slideToMove to the beggining of the Presentation. Note here that even though we're only moving a single slide, slideObjectIds is still set as an array, but with only a single element. These Request objects can then be used to update a presentation using Edit-GSPresentation Edit-GSPresentation -PresentationID $ID -Update $newSlide,$moveSlide This will create a new slide at the end, then move the earlier specified slide to the beggining of the Presentation #> [OutputType('Google.Apis.Slides.v1.Data.Request')] [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Does not change any state')] param ( # Parameter help description [Parameter(Mandatory = $true, Position = 1, ValueFromPipelineByPropertyName = $true)] [hashtable] $RequestProperties ) DynamicParam { # Instead of hardcoding each Request Type into a validate set, or simply passing any value as the request type, # we will look at all the properites of a Data.Request (other than Etag) and assign that as the validate set. # This will ensure that if new request types are added, this function will automatically support them, and they will be available for auto complete $requests = ((New-Object 'Google.Apis.Slides.v1.Data.Request').psobject.Properties | Where-Object Name -ne Etag).Name $attributes = New-Object System.Management.Automation.ParameterAttribute $attributes.Mandatory = $true $attributes.Position = 0 $attributes.ValueFromPipelineByPropertyName = $true $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $attributeCollection.Add($attributes) $validateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($requests) $attributeCollection.Add($validateSetAttribute) $dynParam1 = New-Object System.Management.Automation.RuntimeDefinedParameter("RequestType", [string], $attributeCollection) $paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $paramDictionary.Add("RequestType", $dynParam1) $paramDictionary } begin { } process { try { $RequestType = $PSBoundParameters['RequestType'] #Because of the Dynamic Parameters, this variable won't be automatically created like a regular parameter # Requests that take an array of data require the data to be in a Generic.List (and will throw an unclear error if an array is used). # However this can be cumbersome to construct in PowerShell, and will not automatically cast from a simple array. # This will look through all the properties of the request, find any that are simple arrays, # and convert them to a Generic.List of the appropriate type before creating the request object. $correctedRequest = @{} Write-Verbose "Processing RequestType $RequestType with RequestProperties $($RequestProperties | ConvertTo-Json -Compress)" foreach ($key in $RequestProperties.keys) { if ($RequestProperties[$key] -is 'System.Array') { $type = $RequestProperties[$key][0].GetType().FullName Write-Verbose "Converting $key to Generic.List of type $type" $obj = New-Object System.Collections.Generic.List[$type] foreach ($item in $RequestProperties[$key]) { $obj.Add($item) } $correctedRequest[$key] = $obj } else { Write-Verbose "Adding $key to Request as-is" $correctedRequest[$key] = $RequestProperties[$key] } } Write-Verbose ("Generating new Request Google.Apis.Slides.v1.Data.$RequestType" + "Request") $request = New-Object ("Google.Apis.Slides.v1.Data.$RequestType" + "Request") -Property $correctedRequest Write-Verbose "Generating new Google.Apis.Slides.v1.Data.Request" New-Object Google.Apis.Slides.v1.Data.Request -Property @{$RequestType = $request} } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } end { } } Export-ModuleMember -Function 'New-GSPresentationUpdateRequest' function Get-GSUser { <# .SYNOPSIS Gets the specified G SUite User or a list of Users .DESCRIPTION Gets the specified G SUite User. Designed for parity with Get-ADUser as much as possible .PARAMETER User The primary email or UserID of the user who you are trying to get info 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. Defaults to the AdminEmail in the config .PARAMETER Filter Query string for searching user fields For more information on constructing user queries, see: https://developers.google.com/admin-sdk/directory/v1/guides/search-users PowerShell filter syntax here is supported as "best effort". Please use Google's filter operators and syntax to ensure best results .PARAMETER Domain The specific domain you would like to list users for. Useful for customers with multiple domains. .PARAMETER SearchBase The organizational unit path that you would like to list users from .PARAMETER SearchScope The depth at which to return the list of users Available values are: * "Base": only return the users specified in the SearchBase * "Subtree": return the full list of users underneath the specified SearchBase * "OneLevel": return the SearchBase and the Users directly underneath it .PARAMETER ShowDeleted Returns deleted users .PARAMETER Projection What subset of fields to fetch for this user Acceptable values are: * "Basic": Do not include any custom fields for the user * "Custom": Include custom fields from schemas requested in customFieldMask * "Full": Include all fields associated with this user (default for this module) .PARAMETER CustomFieldMask A comma-separated list of schema names. All fields from these schemas are fetched. This should only be set when using '-Projection Custom' .PARAMETER ViewType Whether to fetch the administrator-only or domain-wide public view of the user. For more information, see Retrieve a user as a non-administrator Acceptable values are: * "Admin_View": Results include both administrator-only and domain-public fields for the user. (default) * "Domain_Public": Results only include fields for the user that are publicly visible to other users in the domain. .PARAMETER Fields The specific fields to fetch for this user .PARAMETER PageSize Page size of the result set .PARAMETER Limit The maximum amount of results you want returned. Exclude or set to 0 to return all results .PARAMETER OrderBy Property to use for sorting results. Acceptable values are: * "Email": Primary email of the user. * "FamilyName": User's family name. * "GivenName": User's given name. .PARAMETER SortOrder Whether to return results in ascending or descending order. Acceptable values are: * "Ascending": Ascending order. * "Descending": Descending order. .EXAMPLE Get-GSUser Gets the user info for the AdminEmail on the config .EXAMPLE Get-GSUser -Filter * Gets the list of users .EXAMPLE Get-GSUser -Filter "IsAdmin -eq '$true'" Gets the list of SuperAdmin users .EXAMPLE Get-GSUser -Filter "IsEnrolledIn2Sv -eq '$false'" -SearchBase /Contractors -SearchScope Subtree Gets the list of users not currently enrolled in 2-Step Verification from the Contractors OrgUnit or any OrgUnits underneath it #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.User')] [cmdletbinding(DefaultParameterSetName = "Get")] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = "Get")] [Alias("PrimaryEmail","UserKey","Mail","Email","Id")] [ValidateNotNullOrEmpty()] [String[]] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false,ParameterSetName = "List")] [Alias("Query")] [String[]] $Filter = '*', [parameter(Mandatory = $false,ParameterSetName = "List")] [String] $Domain, [parameter(Mandatory = $false,ParameterSetName = "List")] [Alias("OrgUnitPath")] [String] $SearchBase, [parameter(Mandatory = $false,ParameterSetName = "List")] [ValidateSet("Base","OneLevel","Subtree")] [String] $SearchScope = "Subtree", [parameter(Mandatory = $false,ParameterSetName = "List")] [Switch] $ShowDeleted, [parameter(Mandatory = $false)] [ValidateSet("Basic","Custom","Full")] [string] $Projection = "Full", [parameter(Mandatory = $false)] [String] $CustomFieldMask, [parameter(Mandatory = $false)] [ValidateSet("Admin_View","Domain_Public")] [String] $ViewType = "Admin_View", [parameter(Mandatory = $false)] [String[]] $Fields, [parameter(Mandatory = $false,ParameterSetName = "List")] [ValidateRange(1,500)] [Alias("MaxResults")] [Int] $PageSize = 500, [parameter(Mandatory = $false,ParameterSetName = "List")] [Alias('First')] [Int] $Limit = 0, [parameter(Mandatory = $false,ParameterSetName = "List")] [ValidateSet("Email","GivenName","FamilyName")] [String] $OrderBy, [parameter(Mandatory = $false,ParameterSetName = "List")] [ValidateSet("Ascending","Descending")] [String] $SortOrder ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.user.readonly' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { if ($MyInvocation.InvocationName -ne 'Get-GSUserList' -and $PSCmdlet.ParameterSetName -eq 'Get') { foreach ($U in $User) { try { Resolve-Email ([ref]$U) Write-Verbose "Getting User '$U'" $request = $service.Users.Get($U) $request.Projection = $Projection $request.ViewType = ($ViewType -replace '_','') if ($CustomFieldMask) { $request.CustomFieldMask = $CustomFieldMask } if ($Fields) { $request.Fields = "$($Fields -join ",")" } $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -Force -PassThru | Add-Member -MemberType ScriptMethod -Name ToString -Value {$this.PrimaryEmail} -PassThru -Force } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } else { try { $request = $service.Users.List() $request.Projection = $Projection if ($PSBoundParameters.Keys -contains 'Domain') { $verbScope = "domain '$($PSBoundParameters['Domain'])'" $request.Domain = $PSBoundParameters['Domain'] } elseif ($Script:PSGSuite.Preference) { switch ($Script:PSGSuite.Preference) { Domain { $verbScope = "domain '$($Script:PSGSuite.Domain)'" $request.Domain = $Script:PSGSuite.Domain } CustomerID { $verbScope = "customer '$($Script:PSGSuite.CustomerID)'" $request.Customer = "$($Script:PSGSuite.CustomerID)" } } } else { $verbScope = "customer 'my_customer'" $request.Customer = "my_customer" } 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) $PageSize = $Limit } $request.MaxResults = $PageSize foreach ($prop in $PSBoundParameters.Keys | Where-Object {$_ -in @('OrderBy','SortOrder','CustomFieldMask','ShowDeleted','ViewType')}) { $request.$prop = $PSBoundParameters[$prop] } if (![String]::IsNullOrEmpty($Filter) -or $SearchBase) { if ($Filter -eq '*') { $Filter = "" } else { $Filter = "$($Filter -join " ")" } if ($SearchBase) { $Filter += " OrgUnitPath='$SearchBase'" } $Filter = $Filter -replace " -eq ","=" -replace " -like ",":" -replace " -match ",":" -replace " -contains ",":" -creplace "'True'","True" -creplace "'False'","False" $request.Query = $Filter.Trim() if ([String]::IsNullOrEmpty($Filter.Trim())) { Write-Verbose "Getting all Users for $verbScope" } else { Write-Verbose "Getting Users for $verbScope matching filter: `"$($Filter.Trim())`"" } } else { Write-Verbose "Getting all Users for $verbScope" } $response = New-Object System.Collections.ArrayList [int]$i = 1 $overLimit = $false do { $result = $request.Execute() if ($result.UsersValue) { $result.UsersValue | ForEach-Object { $_ | Add-Member -MemberType NoteProperty -Name 'User' -Value $_.PrimaryEmail -Force -PassThru | Add-Member -MemberType ScriptMethod -Name ToString -Value {$this.PrimaryEmail} -Force [void]$response.Add($_) } } $request.PageToken = $result.NextPageToken [int]$retrieved = ($i + $result.UsersValue.Count) - 1 Write-Verbose "Retrieved $retrieved users..." if ($Limit -gt 0 -and $retrieved -eq $Limit) { Write-Verbose "Limit reached: $Limit" $overLimit = $true } elseif ($Limit -gt 0 -and ($retrieved + $PageSize) -gt $Limit) { $newPS = $Limit - $retrieved Write-Verbose ("Reducing PageSize from {0} to {1} to meet limit with next page" -f $PageSize,$newPS) $request.MaxResults = $newPS } [int]$i = $i + $result.UsersValue.Count } until ($overLimit -or !$result.NextPageToken) if ($SearchScope -ne "Subtree") { if (!$SearchBase) { $SearchBase = "/" } $response = switch ($SearchScope) { Base { $response | Where-Object {$_.OrgUnitPath -eq $SearchBase} } OneLevel { $maxDepth = ($SearchBase -split "/" | Where-Object {$_}).Count + 1 $children = $response | Select-Object -ExpandProperty OrgUnitPath -Unique | ForEach-Object { if (($_ -split "/" | Where-Object {$_}).Count -le $maxDepth) { $_ } } $response | Where-Object {$_.OrgUnitPath -in $children} } } Write-Verbose "Total users in SearchScope: $($response.Count)" } return $response } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Get-GSUser' function Get-GSUserAlias { <# .SYNOPSIS Gets the specified G SUite User's aliases .DESCRIPTION Gets the specified G SUite User's aliases .PARAMETER User The primary email or UserID of the user 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. Defaults to the AdminEmail in the config .EXAMPLE Get-GSUserAlias Gets the list of aliases for the AdminEmail user #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Alias')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail","Email")] [ValidateNotNullOrEmpty()] [String[]] $User = $Script:PSGSuite.AdminEmail ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.user.readonly' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { foreach ($U in $User) { try { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } Write-Verbose "Getting Alias list for User '$U'" $request = $service.Users.Aliases.List($U) $request.Execute() | Select-Object -ExpandProperty AliasesValue } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Get-GSUserAlias' function Get-GSUserPhoto { <# .SYNOPSIS Gets the photo data for the specified user .DESCRIPTION Gets the photo data for the specified user .PARAMETER User The primary email or UserID of the user who you are trying to get info 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. Defaults to the AdminEmail in the config .PARAMETER OutFilePath The directory path that you would like to save the photos to. If excluded, this will return the photo information .PARAMETER OutFileFormat The format that you would like to save the photo as. Available values are: * "PNG": saves the photo in .png format * "JPG": saves the photo in .jpg format * "Base64": saves the photo as a .txt file containing standard (non-WebSafe) Base64 content. Defaults to PNG .EXAMPLE Get-GSUserPhoto -OutFilePath . Saves the Google user photo of the AdminEmail in the current working directory as a .png image #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.UserPhoto')] [cmdletbinding()] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = "Get")] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String[]] $User = $Script:PSGSuite.AdminEmail, [parameter(Mandatory = $false)] [ValidateScript({(Get-Item $_).PSIsContainer})] [String] $OutFilePath, [parameter(Mandatory = $false)] [ValidateSet('Base64','PNG','JPG')] [String] $OutFileFormat = 'PNG' ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.user.readonly' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { foreach ($U in $User) { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } Write-Verbose "Getting photo for User '$U'" $request = $service.Users.Photos.Get($U) $res = $request.Execute() $base64 = $res.PhotoData | Convert-Base64 -From WebSafeBase64String -To Base64String $bytes = [Convert]::FromBase64String($base64) if ($OutFilePath) { $fileBaseName = "$($U -replace '@.*','')" switch ($OutFileFormat) { JPG { $filePath = Join-Path $OutFilePath "$($fileBaseName).jpg" Write-Verbose "Saving photo at '$filePath'" [System.IO.File]::WriteAllBytes($filePath, $bytes) } PNG { $filePath = Join-Path $OutFilePath "$($fileBaseName).png" Write-Verbose "Saving photo at '$filePath'" [System.IO.File]::WriteAllBytes($filePath, $bytes) } Base64 { $filePath = Join-Path $OutFilePath "$($fileBaseName).txt" Write-Verbose "Saving Base64 photo content at '$filePath'" [System.IO.File]::WriteAllText($filePath,$base64) } } } else { $res | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru | Add-Member -MemberType NoteProperty -Name 'PhotoBytes' -Value $bytes -PassThru | Add-Member -MemberType NoteProperty -Name 'PhotoBase64' -Value $base64 -PassThru } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Get-GSUserPhoto' 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 } } } } } } Export-ModuleMember -Function 'Invoke-GSUserOffboarding' function New-GSUser { <# .SYNOPSIS Creates a new G Suite user .DESCRIPTION Creates a new G Suite user .PARAMETER PrimaryEmail The primary email for the user. If a user with the desired email already exists, a GoogleApiException will be thrown .PARAMETER GivenName The given (first) name of the user .PARAMETER FamilyName The family (last) name of the user .PARAMETER FullName The full name of the user, if different from "$FirstName $LastName" .PARAMETER Password The password for the user. Requires a SecureString .PARAMETER ChangePasswordAtNextLogin If set, user will need to change their password on their first login .PARAMETER OrgUnitPath The OrgUnitPath to create the user in .PARAMETER Suspended If set, user will be created in a suspended state .PARAMETER Addresses The address objects of the user This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserAddress[]' object type. You can create objects of this type easily by using the function 'Add-GSUserAddress' .PARAMETER Emails The email objects of the user This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserEmail[]' object type. You can create objects of this type easily by using the function 'Add-GSUserEmail' .PARAMETER ExternalIds The externalId objects of the user This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserExternalId[]' object type. You can create objects of this type easily by using the function 'Add-GSUserExternalId' .PARAMETER Ims The IM objects of the user This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserIm[]' object type. You can create objects of this type easily by using the function 'Add-GSUserIm' .PARAMETER Locations The Location objects of the user This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserLocation[]' object type. You can create objects of this type easily by using the function 'Add-GSUserLocation' .PARAMETER Organizations The organization objects of the user This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserOrganization[]' object type. You can create objects of this type easily by using the function 'Add-GSUserOrganization' .PARAMETER Relations The relation objects of the user This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserRelation[]' object type. You can create objects of this type easily by using the function 'Add-GSUserRelation' .PARAMETER Phones The phone objects of the user This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserPhone[]' object type. You can create objects of this type easily by using the function 'Add-GSUserPhone' .PARAMETER IncludeInGlobalAddressList Indicates if the user's profile is visible in the G Suite global address list when the contact sharing feature is enabled for the domain. For more information about excluding user profiles, see the administration help center: http://support.google.com/a/bin/answer.py?answer=1285988 .PARAMETER IpWhitelisted If true, the user's IP address is white listed: http://support.google.com/a/bin/answer.py?answer=60752 .PARAMETER CustomSchemas Custom user attribute values to add to the user's account. The Custom Schema and it's fields **MUST** exist prior to updating these values for a user otherwise it will return an error. This parameter only accepts a hashtable where the keys are Schema Names and the value for each key is another hashtable, i.e.: Update-GSUser -User john.smith@domain.com -CustomSchemas @{ schemaName1 = @{ fieldName1 = $fieldValue1 fieldName2 = $fieldValue2 } schemaName2 = @{ fieldName3 = $fieldValue3 } } If you need to CLEAR a custom schema value, simply pass $null as the value(s) for the fieldName in the hashtable, i.e.: Update-GSUser -User john.smith@domain.com -CustomSchemas @{ schemaName1 = @{ fieldName1 = $null fieldName2 = $null } schemaName2 = @{ fieldName3 = $null } } .EXAMPLE $address = Add-GSUserAddress -Country USA -Locality Dallas -PostalCode 75000 Region TX -StreetAddress '123 South St' -Type Work -Primary $phone = Add-GSUserPhone -Type Work -Value "(800) 873-0923" -Primary $extId = Add-GSUserExternalId -Type Login_Id -Value jsmith2 $email = Add-GSUserEmail -Type work -Address jsmith@contoso.com New-GSUser -PrimaryEmail john.smith@domain.com -GivenName John -FamilyName Smith -Password (ConvertTo-SecureString -String 'Password123' -AsPlainText -Force) -ChangePasswordAtNextLogin -OrgUnitPath "/Users/New Hires" -IncludeInGlobalAddressList -Addresses $address -Phones $phone -ExternalIds $extId -Emails $email Creates a user named John Smith and adds their work address, work phone, login_id and alternate non gsuite work email to the user object. #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.User')] [cmdletbinding()] Param ( [parameter(Mandatory = $true, Position = 0)] [String] $PrimaryEmail, [parameter(Mandatory = $true)] [String] $GivenName, [parameter(Mandatory = $true)] [String] $FamilyName, [parameter(Mandatory = $false)] [String] $FullName, [parameter(Mandatory = $true)] [SecureString] $Password, [parameter(Mandatory = $false)] [Switch] $ChangePasswordAtNextLogin, [parameter(Mandatory = $false)] [String] $OrgUnitPath, [parameter(Mandatory = $false)] [Switch] $Suspended, [parameter(Mandatory = $false)] [Google.Apis.Admin.Directory.directory_v1.Data.UserAddress[]] $Addresses, [parameter(Mandatory = $false)] [Google.Apis.Admin.Directory.directory_v1.Data.UserEmail[]] $Emails, [parameter(Mandatory = $false)] [Google.Apis.Admin.Directory.directory_v1.Data.UserExternalId[]] $ExternalIds, [parameter(Mandatory = $false)] [Google.Apis.Admin.Directory.directory_v1.Data.UserIm[]] $Ims, [parameter(Mandatory = $false)] [Google.Apis.Admin.Directory.directory_v1.Data.UserLocation[]] $Locations, [parameter(Mandatory = $false)] [Google.Apis.Admin.Directory.directory_v1.Data.UserOrganization[]] $Organizations, [parameter(Mandatory = $false)] [Google.Apis.Admin.Directory.directory_v1.Data.UserRelation[]] $Relations, [parameter(Mandatory = $false)] [Google.Apis.Admin.Directory.directory_v1.Data.UserPhone[]] $Phones, [parameter(Mandatory = $false)] [Switch] $IncludeInGlobalAddressList, [parameter(Mandatory = $false)] [Switch] $IpWhitelisted, [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)'" $valid = $false } else { $valid = $true } } $valid })] [Hashtable] $CustomSchemas ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.user' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { try { Write-Verbose "Creating user '$PrimaryEmail'" $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.User' $name = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserName' -Property @{ GivenName = $GivenName FamilyName = $FamilyName } if ($PSBoundParameters.Keys -contains 'FullName') { $name.FullName = $FullName } else { $name.FullName = "$GivenName $FamilyName" } $body.Name = $name foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) { switch ($prop) { PrimaryEmail { if ($PSBoundParameters[$prop] -notlike "*@*.*") { $PSBoundParameters[$prop] = "$($PSBoundParameters[$prop])@$($Script:PSGSuite.Domain)" } $body.$prop = $PSBoundParameters[$prop] } Password { $body.Password = (New-Object PSCredential "user", $Password).GetNetworkCredential().Password } Emails { $emailList = New-Object 'System.Collections.Generic.List`1[Google.Apis.Admin.Directory.directory_v1.Data.UserEmail]' foreach ($email in $Emails) { $emailList.Add($email) } $body.Emails = $emailList } ExternalIds { $extIdList = New-Object 'System.Collections.Generic.List`1[Google.Apis.Admin.Directory.directory_v1.Data.UserExternalId]' foreach ($extId in $ExternalIds) { $extIdList.Add($extId) } $body.ExternalIds = $extIdList } Ims { $imList = New-Object 'System.Collections.Generic.List`1[Google.Apis.Admin.Directory.directory_v1.Data.UserIm]' foreach ($im in $Ims) { $imList.Add($im) } $body.Ims = $imList } Locations { $locationList = New-Object 'System.Collections.Generic.List`1[Google.Apis.Admin.Directory.directory_v1.Data.UserLocation]' foreach ($loc in $Locations) { $locationList.Add($loc) } $body.Locations = $locationList } Organizations { $orgList = New-Object 'System.Collections.Generic.List`1[Google.Apis.Admin.Directory.directory_v1.Data.UserOrganization]' foreach ($organization in $Organizations) { $orgList.Add($organization) } $body.Organizations = $orgList } Relations { $relList = New-Object 'System.Collections.Generic.List`1[Google.Apis.Admin.Directory.directory_v1.Data.UserRelation]' foreach ($relation in $Relations) { $relList.Add($relation) } $body.Relations = $relList } Phones { $phoneList = New-Object 'System.Collections.Generic.List`1[Google.Apis.Admin.Directory.directory_v1.Data.UserPhone]' foreach ($phone in $Phones) { $phoneList.Add($phone) } $body.Phones = $phoneList } CustomSchemas { $schemaDict = New-Object 'System.Collections.Generic.Dictionary`2[[System.String],[System.Collections.Generic.IDictionary`2[[System.String],[System.Object]]]]' foreach ($schemaName in $CustomSchemas.Keys) { $fieldDict = New-Object 'System.Collections.Generic.Dictionary`2[[System.String],[System.Object]]' $schemaFields = $CustomSchemas[$schemaName] $schemaFields.Keys | ForEach-Object { $fieldDict.Add($_, $schemaFields[$_]) } $schemaDict.Add($schemaName, $fieldDict) } $body.CustomSchemas = $schemaDict } Default { $body.$prop = $PSBoundParameters[$prop] } } } $request = $service.Users.Insert($body) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'New-GSUser' function New-GSUserAlias { <# .SYNOPSIS Creates a new alias for a G Suite user .DESCRIPTION Creates a new alias for a G Suite user .PARAMETER User The user to create the alias for .PARAMETER Alias The alias or list of aliases to create for the user .EXAMPLE New-GSUserAlias -User john.smith@domain.com -Alias 'jsmith@domain.com','johns@domain.com' Creates 2 new aliases for user John Smith as 'jsmith@domain.com' and 'johns@domain.com' #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.Alias')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail","Email")] [ValidateNotNullOrEmpty()] [String] $User, [parameter(Mandatory = $true,Position = 1)] [String[]] $Alias ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.user' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { foreach ($A in $Alias) { try { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } if ($A -notlike "*@*.*") { $A = "$($A)@$($Script:PSGSuite.Domain)" } Write-Verbose "Creating alias '$A' for user '$User'" $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.Alias' $body.AliasValue = $A $request = $service.Users.Aliases.Insert($body,$User) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'New-GSUserAlias' function Remove-GSUser { <# .SYNOPSIS Removes a user .DESCRIPTION Removes a user .PARAMETER User The primary email or unique Id of the user to Remove-GSUser .EXAMPLE Remove-GSUser joe -Confirm:$false Removes the user 'joe@domain.com', skipping confirmation #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String[]] $User ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.user' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { foreach ($U in $User) { try { if ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } if ($PSCmdlet.ShouldProcess("Deleting user '$U'")) { Write-Verbose "Deleting user '$U'" $request = $service.Users.Delete($U) $request.Execute() Write-Verbose "User '$U' has been successfully deleted" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Remove-GSUser' function Remove-GSUserAlias { <# .SYNOPSIS Removes an alias from a G Suite user .DESCRIPTION Removes an alias from a G Suite user .PARAMETER User The user to remove the alias from .PARAMETER Alias The alias or list of aliases to remove from the user .EXAMPLE Remove-GSUserAlias -User john.smith@domain.com -Alias 'jsmith@domain.com','johns@domain.com' Removes 2 aliases from user John Smith: 'jsmith@domain.com' and 'johns@domain.com' #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail","Email")] [ValidateNotNullOrEmpty()] [String] $User, [parameter(Mandatory = $true,Position = 1)] [String[]] $Alias ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.user' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { foreach ($A in $Alias) { try { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } if ($A -notlike "*@*.*") { $A = "$($A)@$($Script:PSGSuite.Domain)" } if ($PSCmdlet.ShouldProcess("Removing alias '$A' from user '$User'")) { Write-Verbose "Removing alias '$A' from user '$User'" $request = $service.Users.Aliases.Delete($User,$A) $request.Execute() Write-Verbose "Alias '$A' has been successfully deleted from user '$User'" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Remove-GSUserAlias' function Remove-GSUserPhoto { <# .SYNOPSIS Removes the photo for the specified user .DESCRIPTION Removes the photo for the specified user .PARAMETER User The primary email or UserID of the user who you are trying to remove the photo 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 Remove-GSUserPhoto -User me Removes the Google user photo of the AdminEmail user #> [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String[]] $User ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.user' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { foreach ($U in $User) { try { if ($U -ceq 'me') { $U = $Script:PSGSuite.AdminEmail } elseif ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } if ($PSCmdlet.ShouldProcess("Removing the photo for User '$U'")) { Write-Verbose "Removing the photo for User '$U'" $request = $service.Users.Photos.Delete($U) $request.Execute() Write-Verbose "Successfully removed the photo for user '$U'" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Remove-GSUserPhoto' function Restore-GSUser { <# .SYNOPSIS Restores a deleted user .DESCRIPTION Restores a deleted user .PARAMETER User The email address of the user to restore .PARAMETER Id The unique Id of the user to restore .PARAMETER OrgUnitPath The OrgUnitPath to restore the user to Defaults to the root OrgUnit "/" .PARAMETER RecentOnly If multiple users with the email address are found in deleted users, this forces restoration of the most recently deleted user. If not passed and multiple deleted users are found with the specified email address, you will be prompted to choose which you'd like to restore based on deletion time .EXAMPLE Restore-GSUser -User john.smith@domain.com -OrgUnitPath "/Users/Rehires" -Confirm:$false Restores user John Smith to the OrgUnitPath "/Users/Rehires", skipping confirmation. If multiple accounts with the email "john.smith@domain.com" are found, the user is presented with a dialog to choose which account to restore based on deletion time #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.User')] [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "Medium",DefaultParameterSetName = 'User')] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = 'User')] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String[]] $User, [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = 'Id')] [Int] $Id, [parameter(Mandatory = $false,Position = 1)] [String] $OrgUnitPath = "/", [parameter(Mandatory = $false)] [Switch] $RecentOnly ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.user' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams if (!$User) { $User = ($Id | ForEach-Object {"$_"}) $GetId = $false } } Process { try { foreach ($U in $User) { $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserUndelete' $body.OrgUnitPath = $OrgUnitPath if (!$Id) { if ($PSCmdlet.ShouldProcess("Undeleting user '$U'")) { if ($U -notlike "*@*.*") { $U = "$($U)@$($Script:PSGSuite.Domain)" } $delUsers = Get-GSUser -Filter "email=$U" -ShowDeleted -Verbose:$false | Where-Object {$_.PrimaryEmail -eq $U} | Sort-Object DeletionTime -Descending $userId = if ($delUsers.Count -gt 1 -and !$RecentOnly) { $i = 0 $options = @() $idHash = @{} $delUsers | ForEach-Object { $i++ $optText = "$($i): $($_.DeletionTime.ToString())" $options += @{"&$($optText)" = "User '$($_.PrimaryEmail)' deleted on $($_.DeletionTime.ToLongDateString()) at $($_.DeletionTime.ToLongTimeString())"} $idHash[$optText] = $_.Id } $choice = Read-Prompt -Options $options -Title "`n** Choose Which User To Undelete **`n" -Message "There are $($delUsers.Count) deleted users with the email address '$U'. Please enter the number for the user you would like to undelete based on the time the account was deleted`n" $idHash[$choice] } else { $delUsers[0].Id } } } else { $userId = $U } Write-Verbose "Undeleting User Id '$userId' [$U]" $request = $service.Users.Undelete($body,$userId) $request.Execute() Write-Verbose "User '$U' has been successfully undeleted" } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Restore-GSUser' function Sync-GSUserCache { <# .SYNOPSIS Syncs your GS Users to a hashtable contained in the global scoped variable $global:GSUserCache for fast lookups in scripts. .DESCRIPTION Syncs your GS Users to a hashtable contained in the global scoped variable $global:GSUserCache for fast lookups in scripts. .PARAMETER Filter The filter to use with Get-GSUser to populate your UserCache with. Defaults to * (all users). If you'd like to limit to just Active (not suspended) users, use the following filter: "IsSuspended -eq '$false'" .PARAMETER Keys The user properties to use as keys in the Cache hash. Available values are: * PrimaryEmail * Id * Alias Defaults to all 3. .PARAMETER PassThru If $true, returns the hashtable as output .EXAMPLE Sync-GSUserCache -Filter 'IsSuspended=False' Fills the $global:GSUserCache hashtable with all active users using the default Keys. #> [CmdLetBinding()] Param ( [parameter(Mandatory = $false, Position = 0)] [String[]] $Filter = @('*'), [parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [ValidateSet('PrimaryEmail','Id','Alias')] [String[]] $Keys = @('PrimaryEmail','Id','Alias'), [parameter(Mandatory = $false)] [Switch] $PassThru ) Begin { $global:GSUserCache = @{} } Process { Write-Verbose "Syncing users to `$global:GSUserCache" Get-GSUser -Filter $Filter | ForEach-Object { if ($Keys -contains 'Id') { $global:GSUserCache[$_.Id] = $_ } if ($Keys -contains 'PrimaryEmail') { $global:GSUserCache[$_.PrimaryEmail] = $_ } if ($Keys -contains 'Alias') { foreach ($email in $_.Emails.Address) { if (-not ($global:GSUserCache.ContainsKey($email))) { $global:GSUserCache[$email] = $_ } } } } } End { if ($PassThru) { return $global:GSUserCache } } } Export-ModuleMember -Function 'Sync-GSUserCache' function Update-GSUser { <# .SYNOPSIS Updates a user .DESCRIPTION Updates a user .PARAMETER User The primary email or unique Id of the user to update .PARAMETER PrimaryEmail The new primary email for the user. The previous primary email will become an alias automatically .PARAMETER GivenName The new given (first) name for the user .PARAMETER FamilyName The new family (last) name for the user .PARAMETER FullName The new full name for the user .PARAMETER Password The new password for the user as a SecureString .PARAMETER ChangePasswordAtNextLogin If set, user will need to change their password on their next login .PARAMETER OrgUnitPath The new OrgUnitPath for the user .PARAMETER Suspended If set to $true or passed as a bare switch (-Suspended), user will be suspended. If set to $false, user will be unsuspended. If excluded, user's suspension status will remain as-is .PARAMETER Addresses The address objects of the user This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserAddress[]' object type. You can create objects of this type easily by using the function 'Add-GSUserAddress' .PARAMETER Emails The email objects of the user This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserEmail[]' object type. You can create objects of this type easily by using the function 'Add-GSUserEmail' .PARAMETER ExternalIds The externalId objects of the user This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserExternalId[]' object type. You can create objects of this type easily by using the function 'Add-GSUserExternalId' To CLEAR all values for a user, pass `$null` as the value for this parameter. .PARAMETER Ims The IM objects of the user This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserIm[]' object type. You can create objects of this type easily by using the function 'Add-GSUserIm' To CLEAR all values for a user, pass `$null` as the value for this parameter. .PARAMETER Locations The Location objects of the user This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserLocation[]' object type. You can create objects of this type easily by using the function 'Add-GSUserLocation' To CLEAR all values for a user, pass `$null` as the value for this parameter. .PARAMETER Organizations The organization objects of the user This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserOrganization[]' object type. You can create objects of this type easily by using the function 'Add-GSUserOrganization' To CLEAR all values for a user, pass `$null` as the value for this parameter. .PARAMETER RecoveryEmail Recovery email of the user. .PARAMETER RecoveryPhone Recovery phone of the user. The phone number must be in the E.164 format, starting with the plus sign (+). Example: +16506661212. The value provided for RecoveryPhone is stripped of all non-digit characters and prepended with a + to ensure correct formatting. .PARAMETER Relations A list of the user's relationships to other users. This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserRelation[]' object type. You can create objects of this type easily by using the function 'Add-GSUserRelation' To CLEAR all values for a user, pass `$null` as the value for this parameter. .PARAMETER Phones The phone objects of the user This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserPhone[]' object type. You can create objects of this type easily by using the function 'Add-GSUserPhone' To CLEAR all values for a user, pass `$null` as the value for this parameter. .PARAMETER IncludeInGlobalAddressList Indicates if the user's profile is visible in the G Suite global address list when the contact sharing feature is enabled for the domain. For more information about excluding user profiles, see the administration help center: http://support.google.com/a/bin/answer.py?answer=1285988 .PARAMETER IpWhitelisted If true, the user's IP address is white listed: http://support.google.com/a/bin/answer.py?answer=60752 .PARAMETER IsAdmin If true, the user will be made a SuperAdmin. If $false, the user will have SuperAdmin privileges revoked. 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.: Update-GSUser -User john.smith@domain.com -CustomSchemas @{ schemaName1 = @{ fieldName1 = $fieldValue1 fieldName2 = $fieldValue2 } schemaName2 = @{ fieldName3 = $fieldValue3 } } If you need to CLEAR a custom schema value, simply pass $null as the value(s) for the fieldName in the hashtable, i.e.: Update-GSUser -User john.smith@domain.com -CustomSchemas @{ schemaName1 = @{ fieldName1 = $null fieldName2 = $null } schemaName2 = @{ fieldName3 = $null } } The Custom Schema and it's fields **MUST** exist prior to updating these values for a user otherwise it will return an error. .EXAMPLE Update-GSUser -User john.smith@domain.com -PrimaryEmail johnathan.smith@domain.com -GivenName Johnathan -Suspended:$false Updates user john.smith@domain.com with a new primary email of "johnathan.smith@domain.com", sets their Given Name to "Johnathan" and unsuspends them. Their previous primary email "john.smith@domain.com" will become an alias on their account automatically #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.User')] [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "High",DefaultParameterSetName = "NamePart")] Param ( [parameter(Mandatory = $true, Position = 0, ValueFromPipelineByPropertyName = $true)] [Alias("Id", "UserKey", "Mail")] [ValidateNotNullOrEmpty()] [String[]] $User, [parameter(Mandatory = $false)] [String] $PrimaryEmail, [parameter(Mandatory = $false,ParameterSetName = "NamePart")] [String] $GivenName, [parameter(Mandatory = $false,ParameterSetName = "NamePart")] [String] $FamilyName, [parameter(Mandatory = $false,ParameterSetName = "FullName")] [String] $FullName, [parameter(Mandatory = $false)] [SecureString] $Password, [parameter(Mandatory = $false)] [Switch] $ChangePasswordAtNextLogin, [parameter(Mandatory = $false)] [String] $OrgUnitPath, [parameter(Mandatory = $false)] [Switch] $Suspended, [parameter(Mandatory = $false)] [Google.Apis.Admin.Directory.directory_v1.Data.UserAddress[]] $Addresses, [parameter(Mandatory = $false)] [Google.Apis.Admin.Directory.directory_v1.Data.UserEmail[]] $Emails, [parameter(Mandatory = $false)] [Google.Apis.Admin.Directory.directory_v1.Data.UserExternalId[]] $ExternalIds, [parameter(Mandatory = $false)] [Google.Apis.Admin.Directory.directory_v1.Data.UserIm[]] $Ims, [parameter(Mandatory = $false)] [Google.Apis.Admin.Directory.directory_v1.Data.UserLocation[]] $Locations, [parameter(Mandatory = $false)] [Google.Apis.Admin.Directory.directory_v1.Data.UserOrganization[]] $Organizations, [parameter(Mandatory = $false)] [Google.Apis.Admin.Directory.directory_v1.Data.UserRelation[]] $Relations, [parameter(Mandatory = $false)] [String] $RecoveryEmail, [parameter(Mandatory = $false)] [String] $RecoveryPhone, [parameter(Mandatory = $false)] [Google.Apis.Admin.Directory.directory_v1.Data.UserPhone[]] $Phones, [parameter(Mandatory = $false)] [Switch] $IncludeInGlobalAddressList, [parameter(Mandatory = $false)] [Switch] $IpWhitelisted, [parameter(Mandatory = $false)] [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 values must also be a hashtable. The key '$schemaName' has a value of type '$($hash[$schemaName].GetType().Name)'" $valid = $false } else { $valid = $true } } $valid })] [Hashtable] $CustomSchemas ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.user' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams } Process { foreach ($U in $User) { try { Resolve-Email ([ref]$U) if ($PSCmdlet.ShouldProcess("Updating user '$U'")) { Write-Verbose "Updating user '$U'" $userObj = Get-GSUser $U -Verbose:$false $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.User' $name = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserName' $nameUpdated = $false $toClear = @{ } foreach ($prop in $PSBoundParameters.Keys | Where-Object { $body.PSObject.Properties.Name -contains $_ -or $name.PSObject.Properties.Name -contains $_ }) { switch ($prop) { PrimaryEmail { if ($PSBoundParameters[$prop] -notlike "*@*.*") { $PSBoundParameters[$prop] = "$($PSBoundParameters[$prop])@$($Script:PSGSuite.Domain)" } $body.$prop = $PSBoundParameters[$prop] } GivenName { $name.$prop = $PSBoundParameters[$prop] $nameUpdated = $true } FamilyName { $name.$prop = $PSBoundParameters[$prop] $nameUpdated = $true } FullName { $fName = $PSBoundParameters[$prop] if ($fName -match ',') { $splitName = ($fName -split ',',2).Trim() $name.FamilyName = $splitName[0] $name.GivenName = $splitName[1] } else { $splitName = ($fName -split ' ').Trim() $name.FamilyName = $splitName[-1] $name.GivenName = $splitName[0..$($splitName.Count - 2)] -join " " } $nameUpdated = $true } Password { $body.Password = (New-Object PSCredential "user", $Password).GetNetworkCredential().Password } CustomSchemas { $schemaDict = New-Object 'System.Collections.Generic.Dictionary`2[[System.String],[System.Collections.Generic.IDictionary`2[[System.String],[System.Object]]]]' foreach ($schemaName in $CustomSchemas.Keys) { $fieldDict = New-Object 'System.Collections.Generic.Dictionary`2[[System.String],[System.Object]]' $schemaFields = $CustomSchemas[$schemaName] $schemaFields.Keys | ForEach-Object { $fieldDict.Add($_, $schemaFields[$_]) } $schemaDict.Add($schemaName, $fieldDict) } $body.CustomSchemas = $schemaDict } Emails { $emailList = New-Object 'System.Collections.Generic.List`1[Google.Apis.Admin.Directory.directory_v1.Data.UserEmail]' foreach ($email in $Emails) { $emailList.Add($email) } $body.Emails = $emailList } ExternalIds { if ($null -ne $ExternalIds) { $extIdList = New-Object 'System.Collections.Generic.List`1[Google.Apis.Admin.Directory.directory_v1.Data.UserExternalId]' foreach ($extId in $ExternalIds) { $extIdList.Add($extId) } $body.ExternalIds = $extIdList } else { $toClear['externalIds'] = $null } } Ims { if ($null -ne $Ims) { $imList = New-Object 'System.Collections.Generic.List`1[Google.Apis.Admin.Directory.directory_v1.Data.UserIm]' foreach ($im in $Ims) { $imList.Add($im) } $body.Ims = $imList } else { $toClear['ims'] = $null } } Locations { if ($null -ne $Locations) { $locationList = New-Object 'System.Collections.Generic.List`1[Google.Apis.Admin.Directory.directory_v1.Data.UserLocation]' foreach ($loc in $Locations) { $locationList.Add($loc) } $body.Locations = $locationList } else { $toClear['locations'] = $null } } Organizations { if ($null -ne $Organizations) { $orgList = New-Object 'System.Collections.Generic.List`1[Google.Apis.Admin.Directory.directory_v1.Data.UserOrganization]' foreach ($organization in $Organizations) { $orgList.Add($organization) } $body.Organizations = $orgList } else { $toClear['organizations'] = $null } } Relations { if ($null -ne $Relations) { $relList = New-Object 'System.Collections.Generic.List`1[Google.Apis.Admin.Directory.directory_v1.Data.UserRelation]' foreach ($relation in $Relations) { $relList.Add($relation) } $body.Relations = $relList } else { $toClear['relations'] = $null } } Phones { if ($null -ne $Phones) { $phoneList = New-Object 'System.Collections.Generic.List`1[Google.Apis.Admin.Directory.directory_v1.Data.UserPhone]' foreach ($phone in $Phones) { $phoneList.Add($phone) } $body.Phones = $phoneList } else { $toClear['phones'] = $null } } IsAdmin { if ($userObj.IsAdmin -eq $PSBoundParameters[$prop]) { Write-Verbose "User '$U' already has IsAdmin set to '$($userObj.IsAdmin)'" } else { if ($PSCmdlet.ShouldProcess("Updating user '$U' to IsAdmin '$($PSBoundParameters[$prop])'")) { Write-Verbose "Updating user '$U' to IsAdmin '$($PSBoundParameters[$prop])'" $adminBody = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserMakeAdmin' -Property @{ Status = $PSBoundParameters[$prop] } $request = $service.Users.MakeAdmin($adminBody, $userObj.Id) $request.Execute() } } } Default { $body.$prop = $PSBoundParameters[$prop] } } } if ($nameUpdated) { $body.Name = $name } $request = $service.Users.Update($body, $userObj.Id) $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru if ($toClear.Keys.Count) { $header = @{ Authorization = "Bearer $(Get-GSToken -Scopes "https://www.googleapis.com/auth/admin.directory.user" -Verbose:$false)" } $uri = [Uri]"https://www.googleapis.com/admin/directory/v1/users/$U" Write-Verbose "Clearing out all values for User '$U' on the following properties: [ $($toClear.Keys -join ", ") ]" $null = Invoke-RestMethod -Method Put -Uri $uri -Headers $header -Body $($toClear | ConvertTo-Json -Depth 5 -Compress) -ContentType 'application/json' -Verbose:$false -ErrorAction Stop } } } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } } Export-ModuleMember -Function 'Update-GSUser' function Update-GSUserPhoto { <# .SYNOPSIS Updates the photo for the specified user .DESCRIPTION Updates the photo for the specified user .PARAMETER User The primary email or UserID of the user who you are trying to update the photo 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. .PARAMETER Path The path of the photo that you would like to update the user with. .EXAMPLE Update-GSUserPhoto -User me -Path .\myphoto.png Updates the Google user photo of the AdminEmail with the image at the specified path #> [OutputType('Google.Apis.Admin.Directory.directory_v1.Data.UserPhoto')] [cmdletbinding()] Param ( [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] [Alias("PrimaryEmail","UserKey","Mail")] [ValidateNotNullOrEmpty()] [String] $User, [parameter(Mandatory = $true,Position = 1)] [ValidateScript({Test-Path $_})] [String] $Path ) Begin { $serviceParams = @{ Scope = 'https://www.googleapis.com/auth/admin.directory.user' ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' } $service = New-GoogleService @serviceParams $Path = (Resolve-Path $Path).Path } Process { try { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } elseif ($User -notlike "*@*.*") { $User = "$($User)@$($Script:PSGSuite.Domain)" } $mimeType = Get-MimeType -File $Path $photoData = [System.Convert]::ToBase64String(([System.IO.File]::ReadAllBytes($Path))) | Convert-Base64 -From Base64String -To WebSafeBase64String $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserPhoto' -Property @{ PhotoData = $photoData MimeType = $mimeType } Write-Verbose "Updating the photo for User '$User' with file '$Path'" $request = $service.Users.Photos.Update($body,$User) $request.Execute() } catch { if ($ErrorActionPreference -eq 'Stop') { $PSCmdlet.ThrowTerminatingError($_) } else { Write-Error $_ } } } } Export-ModuleMember -Function 'Update-GSUserPhoto' Import-GoogleSDK if ($global:PSGSuiteKey -and $MyInvocation.BoundParameters['Debug']) { $prevDebugPref = $DebugPreference $DebugPreference = "Continue" Write-Debug "`$global:PSGSuiteKey is set to a $($global:PSGSuiteKey.Count * 8)-bit key!" $DebugPreference = $prevDebugPref } $aliasHash = # Alias => => => => => => => => Function @{ 'Add-GSDriveFilePermissions' = 'Add-GSDrivePermission' 'Export-PSGSuiteConfiguration' = 'Set-PSGSuiteConfig' 'Get-GSCalendarEventList' = 'Get-GSCalendarEvent' 'Get-GSCalendarResourceList' = 'Get-GSResourceList' 'Get-GSDataTransferApplicationList' = 'Get-GSDataTransferApplication' 'Get-GSDriveFileInfo' = 'Get-GSDriveFile' 'Get-GSDriveFilePermissionsList' = 'Get-GSDrivePermission' 'Get-GSGmailDelegates' = 'Get-GSGmailDelegate' 'Get-GSGmailFilterList' = 'Get-GSGmailFilter' 'Get-GSGmailLabelList' = 'Get-GSGmailLabel' 'Get-GSGmailMessageInfo' = 'Get-GSGmailMessage' 'Get-GSGmailSendAsSettings' = 'Get-GSGmailSendAsAlias' 'Get-GSGmailSignature' = 'Get-GSGmailSendAsAlias' 'Get-GSGroupList' = 'Get-GSGroup' 'Get-GSGroupMemberList' = 'Get-GSGroupMember' 'Get-GSMobileDeviceList' = 'Get-GSMobileDevice' 'Get-GSOrganizationalUnitList' = 'Get-GSOrganizationalUnit' 'Get-GSOrgUnit' = 'Get-GSOrganizationalUnit' 'Get-GSOrgUnitList' = 'Get-GSOrganizationalUnit' 'Get-GSOU' = 'Get-GSOrganizationalUnit' 'Get-GSResourceList' = 'Get-GSResource' 'Get-GSTeamDrive' = 'Get-GSDrive' 'Get-GSTeamDrivesList' = 'Get-GSDrive' 'Get-GSUserASPList' = 'Get-GSUserASP' 'Get-GSUserLicenseInfo' = 'Get-GSUserLicense' 'Get-GSUserLicenseList' = 'Get-GSUserLicense' 'Get-GSUserList' = 'Get-GSUser' 'Get-GSUserSchemaInfo' = 'Get-GSUserSchema' 'Get-GSUserSchemaList' = 'Get-GSUserSchema' 'Get-GSUserTokenList' = 'Get-GSUserToken' 'Import-PSGSuiteConfiguration' = 'Get-PSGSuiteConfig' 'Move-GSGmailMessageToTrash' = 'Remove-GSGmailMessage' 'New-GSCalendarResource' = 'New-GSResource' 'New-GSTeamDrive' = 'New-GSDrive' 'Remove-GSGmailMessageFromTrash' = 'Restore-GSGmailMessage' 'Remove-GSTeamDrive' = 'Remove-GSDrive' 'Set-PSGSuiteDefaultDomain' = 'Switch-PSGSuiteConfig' 'Switch-PSGSuiteDomain' = 'Switch-PSGSuiteConfig' 'Update-GSCalendarResource' = 'Update-GSResource' 'Update-GSGmailSendAsSettings' = 'Update-GSGmailSendAsAlias' 'Update-GSSheetValue' = 'Export-GSSheet' 'Update-GSTeamDrive' = 'Update-GSDrive' } foreach ($key in $aliasHash.Keys) { try { New-Alias -Name $key -Value $aliasHash[$key] -Force } catch { Write-Error "[ALIAS: $($key)] $($_.Exception.Message.ToString())" } } Export-ModuleMember -Alias '*' if (!(Test-Path (Join-Path "~" ".scrthq"))) { New-Item -Path (Join-Path "~" ".scrthq") -ItemType Directory -Force | Out-Null } if ($PSVersionTable.ContainsKey('PSEdition') -and $PSVersionTable.PSEdition -eq 'Core' -and !$Global:PSGSuiteKey -and !$IsWindows) { if (!(Test-Path (Join-Path (Join-Path "~" ".scrthq") "BlockCoreCLREncryptionWarning.txt"))) { Write-Warning "CoreCLR does not support DPAPI encryption! Setting a basic AES key to prevent errors. Please create a unique key as soon as possible as this will only obfuscate secrets from plain text in the Configuration, the key is not secure as is. If you would like to prevent this message from displaying in the future, run the following command: Block-CoreCLREncryptionWarning " } $Global:PSGSuiteKey = [Byte[]]@(1..16) $ConfigScope = "User" } if ($Global:PSGSuiteKey -is [System.Security.SecureString]) { $Method = "SecureString" if (!$ConfigScope) { $ConfigScope = "Machine" } } elseif ($Global:PSGSuiteKey -is [System.Byte[]]) { $Method = "AES Key" if (!$ConfigScope) { $ConfigScope = "Machine" } } else { $Method = "DPAPI" $ConfigScope = "User" } Add-MetadataConverter -Converters @{ [SecureString] = { $encParams = @{} if ($Global:PSGSuiteKey -is [System.Byte[]]) { $encParams["Key"] = $Global:PSGSuiteKey } elseif ($Global:PSGSuiteKey -is [System.Security.SecureString]) { $encParams["SecureKey"] = $Global:PSGSuiteKey } 'ConvertTo-SecureString "{0}"' -f (ConvertFrom-SecureString $_ @encParams) } "Secure" = { param([string]$String) $encParams = @{} if ($Global:PSGSuiteKey -is [System.Byte[]]) { $encParams["Key"] = $Global:PSGSuiteKey } elseif ($Global:PSGSuiteKey -is [System.Security.SecureString]) { $encParams["SecureKey"] = $Global:PSGSuiteKey } ConvertTo-SecureString $String @encParams } "ConvertTo-SecureString" = { param([string]$String) $encParams = @{} if ($Global:PSGSuiteKey -is [System.Byte[]]) { $encParams["Key"] = $Global:PSGSuiteKey } elseif ($Global:PSGSuiteKey -is [System.Security.SecureString]) { $encParams["SecureKey"] = $Global:PSGSuiteKey } ConvertTo-SecureString $String @encParams } } try { $confParams = @{ Scope = $ConfigScope } if ($ConfigName) { $confParams["ConfigName"] = $ConfigName $Script:ConfigName = $ConfigName } try { if ($global:PSGSuite) { Write-Warning "Using config $(if ($global:PSGSuite.ConfigName){"name '$($global:PSGSuite.ConfigName)' "})found in variable: `$global:PSGSuite" Write-Verbose "$(($global:PSGSuite | Format-List | Out-String).Trim())" if ($global:PSGSuite -is [System.Collections.Hashtable]) { $global:PSGSuite = New-Object PSObject -Property $global:PSGSuite } $script:PSGSuite = $global:PSGSuite } else { Get-PSGSuiteConfig @confParams -ErrorAction Stop } } catch { if (Test-Path "$ModuleRoot\$env:USERNAME-$env:COMPUTERNAME-$env:PSGSuiteDefaultDomain-PSGSuite.xml") { Get-PSGSuiteConfig -Path "$ModuleRoot\$env:USERNAME-$env:COMPUTERNAME-$env:PSGSuiteDefaultDomain-PSGSuite.xml" -ErrorAction Stop Write-Warning "No Configuration.psd1 found at scope '$ConfigScope'; falling back to legacy XML. If you would like to convert your legacy XML to the newer Configuration.psd1, run the following command: Get-PSGSuiteConfig -Path '$ModuleRoot\$env:USERNAME-$env:COMPUTERNAME-$env:PSGSuiteDefaultDomain-PSGSuite.xml' -PassThru | Set-PSGSuiteConfig " } else { Write-Warning "There was no config returned! Please make sure you are using the correct key or have a configuration already saved." } } } catch { Write-Warning "There was no config returned! Please make sure you are using the correct key or have a configuration already saved." } |