Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ name: CI
on:
pull_request:

env:
DEBUG: 'true'

jobs:
test:
runs-on: windows-latest
Expand All @@ -15,4 +18,8 @@ jobs:
- run: |
$env:PSModulePath += ";C:\Program Files\Citrix\PowerShellModules"
.\test
shell: powershell
shell: powershell

- name: Setup tmate session
if: ${{ env.DEBUG == 'true' }}
uses: mxschmitt/action-tmate@v3
14 changes: 10 additions & 4 deletions build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@ Import-Module Microsoft.PowerShell.PSResourceGet -ErrorAction Stop

$Author = 'Tony Sathre'
$CompanyName = 'Tony Sathre'
$Description = 'This module is used to automate the deployment of Citrix virtual desktops in a Citrix Virtual Apps & Desktops environment.'
$Description = 'This module is used to automate the creation of Citrix virtual desktops in a Citrix Virtual Apps & Desktops environment.'
$ModuleVersion = '2.0.0.0'
$Copyright = "(c) {0} ${Author}. All rights reserved." -f (Get-Date -Format 'yyyy')
$ProjectUri = 'https://github.com/tonysathre/CitrixAutodeploy'

$BasePath = "${PSScriptRoot}\module\CitrixAutodeploy"
$Path = "${BasePath}\CitrixAutodeploy.psd1"
$RootModule = "${PSScriptRoot}\module\CitrixAutodeploy\CitrixAutodeploy.psm1"
$NestedModules = Get-ChildItem -Recurse ${BasePath}\functions\*.ps1 | ForEach-Object { ".\functions\$(Split-Path -Leaf $_.Directory)\$($_.Name)" }
$FunctionsToExport = (Get-ChildItem ${BasePath}\functions\public\*.ps1).Name -replace '\.ps1$'
$RequiredModules = @('PoShLog')
$ScriptsToProcess = @('.\functions\private\Initialize-InternalLogger.ps1')
#$ScriptsToProcess = @('.\functions\private\Initialize-InternalLogger.ps1')
$VariablesToExport = @('InternalLogger')


Expand All @@ -23,12 +25,16 @@ $ModuleManifest = @{
Copyright = $Copyright
ProjectUri = $ProjectUri
ModuleVersion = $ModuleVersion
Path = "${BasePath}\CitrixAutodeploy.psd1"
RootModule = $RootModule
Path = $Path
FunctionsToExport = $FunctionsToExport
NestedModules = $NestedModules
RequiredModules = $RequiredModules
ScriptsToProcess = $ScriptsToProcess
#ScriptsToProcess = $ScriptsToProcess
VariablesToExport = $VariablesToExport
}

Update-PSModuleManifest @ModuleManifest

# Trim trailing whitespace added by Update-PSModuleManifest
(Get-Content $Path).TrimEnd() | Set-Content $Path
35 changes: 18 additions & 17 deletions module/CitrixAutodeploy/CitrixAutodeploy.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
#
# Generated by: Tony Sathre
#
# Generated on: 11/25/2024
# Generated on: 12/3/2024
#

@{

# Script module or binary module file associated with this manifest.
# RootModule = ''
RootModule = 'C:\Users\m89944\git\tonysathre\CitrixAutoDeploy\module\CitrixAutodeploy\CitrixAutodeploy.psm1'

# Version number of this module.
ModuleVersion = '2.0.0.0'
Expand All @@ -30,7 +30,7 @@ CompanyName = 'Tony Sathre'
Copyright = '(c) 2024 Tony Sathre. All rights reserved.'

# Description of the functionality provided by this module
Description = 'This module is used to automate the deployment of Citrix virtual desktops in a Citrix Virtual Apps & Desktops environment.'
Description = 'This module is used to automate the creation of Citrix virtual desktops in a Citrix Virtual Apps & Desktops environment.'

# Minimum version of the Windows PowerShell engine required by this module
# PowerShellVersion = ''
Expand Down Expand Up @@ -66,23 +66,24 @@ ScriptsToProcess = '.\functions\private\Initialize-InternalLogger.ps1'
# FormatsToProcess = @()

# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
NestedModules = @('.\functions\private\Initialize-InternalLogger.ps1',
'.\functions\public\Get-CtxAutodeployConfig.ps1',
'.\functions\public\Initialize-CtxAutodeployEnv.ps1',
'.\functions\public\Initialize-CtxAutodeployLogger.ps1',
'.\functions\public\Invoke-CtxAutodeployTask.ps1',
'.\functions\public\New-CtxAutodeployVM.ps1',
'.\functions\public\Start-CtxHighLevelLogger.ps1',
'.\functions\public\Stop-CtxHighLevelLogger.ps1',
'.\functions\public\Test-DdcConnection.ps1',
'.\functions\public\Test-MachineCountExceedsLimit.ps1',
NestedModules = @('.\functions\private\Get-PathType.ps1',
'.\functions\private\Initialize-InternalLogger.ps1',
'.\functions\public\Get-CtxAutodeployConfig.ps1',
'.\functions\public\Initialize-CtxAutodeployEnv.ps1',
'.\functions\public\Initialize-CtxAutodeployLogger.ps1',
'.\functions\public\Invoke-CtxAutodeployTask.ps1',
'.\functions\public\New-CtxAutodeployVM.ps1',
'.\functions\public\Start-CtxHighLevelLogger.ps1',
'.\functions\public\Stop-CtxHighLevelLogger.ps1',
'.\functions\public\Test-DdcConnection.ps1',
'.\functions\public\Test-MachineCountExceedsLimit.ps1',
'.\functions\public\Wait-ForIdentityPoolUnlock.ps1')

# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = 'Get-CtxAutodeployConfig', 'Initialize-CtxAutodeployEnv',
'Initialize-CtxAutodeployLogger', 'Invoke-CtxAutodeployTask',
'New-CtxAutodeployVM', 'Start-CtxHighLevelLogger',
'Stop-CtxHighLevelLogger', 'Test-DdcConnection',
FunctionsToExport = 'Get-CtxAutodeployConfig', 'Initialize-CtxAutodeployEnv',
'Initialize-CtxAutodeployLogger', 'Invoke-CtxAutodeployTask',
'New-CtxAutodeployVM', 'Start-CtxHighLevelLogger',
'Stop-CtxHighLevelLogger', 'Test-DdcConnection',
'Test-MachineCountExceedsLimit', 'Wait-ForIdentityPoolUnlock'

# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
Expand Down
10 changes: 9 additions & 1 deletion module/CitrixAutodeploy/CitrixAutodeploy.psm1
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
# TODO: may not even need this
# dot-source private functions
foreach ($Folder in @('private')) {
$Root = Join-Path -Path $PSScriptRoot -ChildPath $Folder
if (Test-Path -Path $Root) {
Write-Verbose "processing folder $Root"
$Files = Get-ChildItem -Path $Root -Filter *.ps1 -Recurse
$Files | ForEach-Object { Write-Verbose $_.basename; . $PSItem.FullName }
}
}
34 changes: 34 additions & 0 deletions module/CitrixAutodeploy/functions/private/Get-PathType.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
function Get-PathType {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$Path
)

Write-VerboseLog -Message 'Function {MyCommand} called with parameters: {PSBoundParameters}' -PropertyValues $MyInvocation.MyCommand, ($PSBoundParameters | Out-String)

if ($Path -match '^https?://') {
return 'Uri'
}

# PowerShell 5.1 uses .NET 4.0.30319.42000
# [System.IO.Path]::GetInvalidPathChars() contains these printable characters: "<>\|☺☻♥♦♣\t\n\f\r►◄↕‼¶§▬↨↑↓→∟↔▲▼
# There are also several non-printable characters in the array.
$InvalidPathChars = [System.IO.Path]::InvalidPathChars
$Pattern = [regex]::Escape(($InvalidPathChars -join ''))

if ($Path -match "[$Pattern]") {
throw [System.IO.IOException]::new('The provided file path contains invalid characters: {0}' -f $Path)
}

if ([System.IO.Path]::GetExtension($Path)) {
return 'LocalFile'
}

if (Test-Path $Path -PathType Container) {
return 'Directory'
}

return 'Unknown'
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,60 @@
function Get-CtxAutodeployConfig {
[CmdletBinding()]
[OutputType([PSCustomObject])]
param(
param (
[Parameter()]
[System.IO.FileInfo]$FilePath = $env:CITRIX_AUTODEPLOY_CONFIG
[ValidateNotNullOrEmpty()]
[string]$Path = $env:CITRIX_AUTODEPLOY_CONFIG
)

Write-VerboseLog -Message "Function {MyCommand} called with parameters: {PSBoundParameters}" -PropertyValues $MyInvocation.MyCommand, ($PSBoundParameters | Out-String)
Write-InfoLog -Message "Loading configuration from file: {FilePath}" -PropertyValues $FilePath
Write-VerboseLog -Message 'Function {MyCommand} called with parameters: {PSBoundParameters}' -PropertyValues $MyInvocation.MyCommand, ($PSBoundParameters | Out-String)

$PathType = Get-PathType -Path $Path
Write-DebugLog -Message 'PathType: {PathType}' -PropertyValues $PathType

switch ($PathType) {
'Uri' {
try {
Write-VerboseLog -Message 'Downloading configuration from URL: {Path}' -PropertyValues $Path
$Response = Invoke-WebRequest -Uri $Path -UseBasicParsing
$ConfigContent = $Response.Content
}
catch {
Write-ErrorLog -Message 'Failed to download the configuration file from URL: {Path}' -Exception $_.Exception -ErrorRecord $_ -PropertyValues $Path
throw
}
}
'LocalFile' {
try {
Write-VerboseLog -Message 'Reading configuration from local file: {Path}' -PropertyValues $Path
$ConfigContent = Get-Content -Path $Path -Raw -ErrorAction Stop

if ($ConfigContent.Length -eq 0) {
Write-ErrorLog -Message 'The configuration file is empty: {Path}' -PropertyValues $Path
throw [System.IO.IOException]::new('The configuration file is empty: {0}' -f $Path)
}
}
catch {
Write-FatalLog -Message 'Failed to read the configuration file from local path: {Path}' -Exception $_.Exception -ErrorRecord $_ -PropertyValues $Path
throw
}
}
'Directory' {
Write-FatalLog -Message 'The provided path is a directory: {Path}' -Exception ([System.Management.Automation.ItemNotFoundException]::new('Directory path')) -PropertyValues $Path
throw
}
default {
Write-FatalLog -Message 'The provided path is neither a valid URL nor a local file path: {Path}' -Exception ([System.Management.Automation.ItemNotFoundException]::new('Invalid path or URL')) -PropertyValues $Path
throw
}
}

try {
$Config = Get-Content -Path $FilePath -Raw -ErrorAction Stop | ConvertFrom-Json
$Config = ConvertFrom-Json -InputObject $ConfigContent

return $Config
}
catch {
Write-FatalLog -Message "Failed to load configuration from file {FilePath}" -Exception $_.Exception -ErrorRecord $_ -PropertyValues $FilePath
Write-ErrorLog -Message 'Failed to parse the configuration content as JSON: {ConfigContent}' -Exception $_.Exception -ErrorRecord $_ -PropertyValues $ConfigContent
throw
}

return $Config
}
20 changes: 11 additions & 9 deletions test.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -31,32 +31,34 @@ param (
try {
if ($PSBoundParameters['Verbose']) {
. ${PSScriptRoot}\module\CitrixAutodeploy\functions\public\Initialize-CtxAutodeployLogger.ps1 4> $null
$VerbosePreference -eq 'Continue'
$Logger = Initialize-CtxAutodeployLogger -LogLevel Verbose -AddEnrichWithExceptionDetails
}

if ($PSBoundParameters['Debug']) {
. ${PSScriptRoot}\module\CitrixAutodeploy\functions\public\Initialize-CtxAutodeployLogger.ps1 4> $null
$DebugPreference -eq 'Continue'
$Logger = Initialize-CtxAutodeployLogger -LogLevel Debug -AddEnrichWithExceptionDetails
}

$PesterConfiguration = New-PesterConfiguration
$PesterConfiguration.Output.Verbosity = $Output
$PesterConfiguration.Run.Path = $Tests
$PesterConfiguration.Output.StackTraceVerbosity = $StackTraceVerbosity
$PesterConfiguration.CodeCoverage.Enabled = $CodeCoverageEnabled
$PesterConfiguration.CodeCoverage.Path = $Tests
$PesterConfiguration.Output.Verbosity = $Output
$PesterConfiguration.Run.Path = $Tests
$PesterConfiguration.Output.StackTraceVerbosity = $StackTraceVerbosity
$PesterConfiguration.CodeCoverage.Enabled = $CodeCoverageEnabled
$PesterConfiguration.CodeCoverage.Path = $Tests
$PesterConfiguration.CodeCoverage.CoveragePercentTarget = 75

1..$Iterations | ForEach-Object {
Invoke-Pester -Configuration $PesterConfiguration
}
}

catch {}
catch {
throw $_
}

finally {
Close-Logger
}

$global:VerbosePreference = 'SilentlyContinue'
$global:DebugPreference = 'SilentlyContinue'
}
Loading