Skip to content
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,18 @@

# PSGSuite - ChangeLog

## 3.x.x - 2025-xx-xx

- Added `-lib` parameter to `Import-GoogleSDK` that defines the path of the directory containing the Google API libraries.
- Added functionality to programmatically generate module components during the module build process. Further details can be found at [ci\templates\README.md](ci\templates\README.md).
- Added two additional tasks `Download` and `Generate` to the module build process.
- `Download` task performs the downloading of NuGet dependencies which was previously performed by the `Compile` task.
- `Generate` task performs the programmatic generation of module content.
- Changed the `Compile` task of the module build process to support including content from the `Module` and `Classes` directories in the compiled `psgsuite.psm1` file. Content from the module source directories will be compiled into the module in the following order; `Class`, `Private`, `Public`, `Module`
- Moved the existing module initialization code out of the `Compile` task of the module build process and split into two parts:
- The dynamic alias logic has been moved into the `templates\Module\Aliases.ps1` template file.
- The static module initialization logic has been moved into the `Module\Initialization.ps1` file.

## 3.0.0 - 2024-11-20

### Breaking Changes
Expand Down
59 changes: 59 additions & 0 deletions PSGSuite/Module/Aliases.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Programmatically generated from template 'module\aliases.ps1'
# This file will be overwritten during the module build process.

$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 '*'
110 changes: 110 additions & 0 deletions PSGSuite/Module/Initialization.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
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
}

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:`n`nBlock-CoreCLREncryptionWarning`n"
}
$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."
}
5 changes: 3 additions & 2 deletions PSGSuite/Private/Import-GoogleSDK.ps1
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
function Import-GoogleSDK {
[CmdletBinding()]
Param()
Param(
[string]$lib = (Resolve-Path "$($script:ModuleRoot)\lib")
)
Process {
$lib = Resolve-Path "$($script:ModuleRoot)\lib"
$refs = @()
$sdkPath = $lib
$dlls = Get-ChildItem $sdkPath -Filter "*.dll"
Expand Down
4 changes: 2 additions & 2 deletions build.ps1
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[cmdletbinding()]
param(
[parameter( Position = 0)]
[ValidateSet('Init','Clean','Compile','Import','Test','Full','Deploy','Skip','Docs')]
[ValidateSet('Init','Clean', 'Download', 'Generate', 'Compile','Import','Test','Full','Deploy','Skip','Docs')]
[string[]]
$Task = @('Init','Clean','Compile','Import'),
$Task = @('Init','Clean', 'Download', 'Generate', 'Compile','Import'),
[parameter()]
[Alias('nr','nor')]
[switch]$NoRestore,
Expand Down
20 changes: 20 additions & 0 deletions ci/templates/Module/Aliases.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Param(
[parameter(mandatory=$true)]$SourceDirectory
)

$aliasHashContents = (Get-Content (Join-Path $SourceDirectory "PSGSuite" "Aliases" "PSGSuite.Aliases.ps1") -Raw).Trim()

@"
`$aliasHash = $aliasHashContents

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 '*'
"@
80 changes: 80 additions & 0 deletions ci/templates/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Generation Templates

This directory contains the templates used to programmatically generate module content during the build process. Each template is an independent `.ps1` file that outputs the generated content to the PowerShell pipeline as either a string or a hashtable of strings.

## Template Directories

By default the template directory tree mirrors the module source directory tree. When executed, all output will be written to the corresponding directory found in the module source directory.

For example, the template `<Repository_Root>\Templates\Public\Authentication\Foo.ps1` will have it's output written to the directory `<Repository_Root>\PSGSuite\Public\Authentication\`.

## Template Priority

It may be beneficial to prioritise the order of execution for each template. It is possible to assign a priority value to each template between `1` execute first and `9` execute last. To assign a value, prefix the template name with `<priority>-`.

If a priority value is not assigned to a template, it will be assigned priority `5` by default.

For example, for the following templates:

- `2-foo.ps1` is assigned priority `2`
- `bar.ps1` is assigned priority `5`

## Template Output

**String Output**

When a template returns a string value it's output filename will be the template file name excluding any priority prefix.

For example, the file `<Repository_Root>\Templates\Public\Authentication\2-Foo.ps1` will have it's output written to the file `<Repository_Root>\PSGSuite\Public\Authentication\Foo.ps1`.

**Hashtable Output**

When a template returns a hashtable, the value for each entry will be converted to a string and the key becomes the output file name including the file extension.

For example, the file `<Repository_Root>\Templates\Public\Authentication\Foo.ps1` produces the following hashtable.

```
@{
'Get-Foo.ps1' = 'Write-Host "Got Foo!"'
'Remove-Foo.ps1' = 'Write-Host "Removed Foo!"'
}
```

Two output files would be produced.

- `<Repository_Root>\PSGSuite\Public\Authentication\Get-Foo.ps1`
- `<Repository_Root>\PSGSuite\Public\Authentication\Remove-Foo.ps1`

So far the output files have been relative to the location of the template file. It is also possible to provide an absolute file path, relative to the source directory, as the entries key. When this is done, the output file path will be the specified absolute path.

For example, the file `<Repository_Root>\Templates\Public\Authentication\Foo.ps1` produces the following hashtable.

```
@{
'\Private\Authentication\Get-Foo.ps1' = 'Write-Host "Got Foo!"'
'\Show-Foo.ps1' = 'Write-Host "Show Foo!"'
'Remove-Foo.ps1' = 'Write-Host "Removed Foo!"'
}
```

Three output files would be produced.

- `<Repository_Root>\PSGSuite\Private\Authentication\Get-Foo.ps1`
- `<Repository_Root>\PSGSuite\Show-Foo.ps1`
- `<Repository_Root>\PSGSuite\Public\Authentication\Remove-Foo.ps1`


**Unsupported Output**

When a template returns an unsupported type, it will be converted and processed as a string.


## Template Execution

Template execution is triggered by the `generate` task of the PSGSuite build pipeline.

The following parameters are passed to the template:

- `-ModuleSource` - This is the file path to the repository root.

All generated output files will each have a comment added at the top of the file indicating it was programmatically generated.
Loading
Loading