diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 2a9b062..aee3fad 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -7,7 +7,7 @@ about: Suggest an idea for this project **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -**Is there and [API](https://help.qlik.com/en-US/nprinting/November2018/APIs/NP+API/index.html) that needs to be implemented? If so which one.** +**Is there and [API](https://help.qlik.com/en-US/nprinting/csh/Content/NPrinting/Extending/NPrinting-APIs-Reference-Redirect.htm) that needs to be implemented? If so which one.** **Describe the solution you'd like** A clear and concise description of what you want to happen. diff --git a/.github/workflows/BuildModule.yml b/.github/workflows/BuildModule.yml new file mode 100644 index 0000000..391cd86 --- /dev/null +++ b/.github/workflows/BuildModule.yml @@ -0,0 +1,65 @@ +# This is a basic workflow to help you get started with Actions + +name: Module Build + +# Controls when the workflow will run +on: + # Triggers the workflow on push or pull request events but only for the "main" branch + push: + branches-ignore: [ 'main', '**-alpha', '**-dev' ] + paths-ignore: + - '.github/workflows/**' + - 'docs/**' + pull_request: + branches: [ "main" ] + paths-ignore: + - '.github/workflows/**' + - 'docs/**' + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + build-module: + name: Build Module Manifest + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v3 + with: + ref: ${{ github.head_ref }} + fetch-depth: 0 + - name: Build the PowerShell Module + id: pwsh-build + shell: pwsh + env: + GH_TOKEN: ${{ secrets.API_TOKEN }} + run: | + Get-ChildItem -Recurse -Include "BuildModule.ps1"|%{. $_.FullName} + - name: Build the Readme + uses: baileyjm02/markdown-to-pdf@v1 + with: + input_dir: ${{ steps.pwsh-build.outputs.ModuleDir }} + output_dir: ${{ steps.pwsh-build.outputs.ModuleDir }} + # Default is true, can set to false to only get PDF files + table_of_contents: value + build_html: true + build_pdf: true + - name: Upload test results + uses: actions/upload-artifact@v3 + id: artifact + with: + name: ${{ steps.pwsh-build.outputs.ModuleName }} + path: ${{ steps.pwsh-build.outputs.ModuleDir }} + - name: Release + uses: softprops/action-gh-release@v1 + if: ${{ github.ref == 'refs/heads/main' }} + with: + token: ${{ secrets.API_TOKEN }} + name: ${{ steps.pwsh-build.outputs.ModuleName }} - ${{ steps.pwsh-build.outputs.Version }} + tag_name: ${{ steps.pwsh-build.outputs.Version }} + generate_release_notes: true + draft: true + files: | + ${{ steps.pwsh-build.outputs.ModuleZip }} diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml new file mode 100644 index 0000000..c192705 --- /dev/null +++ b/.github/workflows/Release.yml @@ -0,0 +1,32 @@ +name: Publish Module + +on: + release: + types: [published] + + workflow_dispatch: + +jobs: + notification: + runs-on: ubuntu-latest + env: + REQUIRED_TOKEN: ${{ secrets.PWSHGALLERY_TOKEN != '' }} + + steps: + - name: Download release asset + if: ${{ env.REQUIRED_TOKEN == 'true' }} + id: download-release-asset + uses: robinraju/release-downloader@v1.5 + with: + latest: true + #zipBall: true + token: ${{ secrets.API_TOKEN }} + - name: Release the PowerShell Module + if: ${{ env.REQUIRED_TOKEN == 'true' }} + id: pwsh-release + shell: pwsh + env: + PWSHGALLERY_TOKEN: ${{ secrets.PWSHGALLERY_TOKEN }} + run: | + Get-ChildItem -File -Filter "*.zip"|Expand-Archive + Get-ChildItem -File -Filter "*.psd1" -Recurse |%{Publish-Module -Path $_.Directory.FullName -NuGetApiKey $ENV:PWSHGALLERY_TOKEN} \ No newline at end of file diff --git a/.gitignore b/.gitignore index e69de29..597dc0d 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,2 @@ +QlikNPrinting-CLI/* +QlikNPrinting-CLI.zip diff --git a/LICENSE b/LICENSE index c49c212..85691db 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 Marc Collins +Copyright (c) 2024 Marc Collins Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Module_Config.json b/Module_Config.json new file mode 100644 index 0000000..f7310fc --- /dev/null +++ b/Module_Config.json @@ -0,0 +1,22 @@ +{ + "RepositoryURI": "https://github.com/QlikProfessionalServices/QlikNPrinting-CLI", + "ModuleName": "QlikNPrinting-CLI", + "ModuleManifest": { + "Author": "Marc Collins", + "CompanyName": "Qlik - Customer Success", + "Copyright": null, + "Description": "The Qlik NPrinting CLI is a PowerShell module designed to interact with and manage Qlik NPrinting environments programmatically", + "DotNetFrameworkVersion": null, + "FileList": null, + "FunctionsToExport": null, + "Guid": "eca92804-c4ca-4aa8-9313-44d71005379d", + "HelpInfoUri": null, + "LicenseUri": null, + "ModuleVersion": "1.2", + "PrivateData": null, + "ProjectUri": null, + "ReleaseNotes": null, + "Tags": null + }, + "AllowDynamicContent": true +} \ No newline at end of file diff --git a/QlikNPrinting-CLI.psm1 b/QlikNPrinting-CLI.psm1 index 513d792..8639897 100644 Binary files a/QlikNPrinting-CLI.psm1 and b/QlikNPrinting-CLI.psm1 differ diff --git a/src/Private/AuthenticateNPrinting.ps1 b/src/Private/AuthenticateNPrinting.ps1 new file mode 100644 index 0000000..e095ca0 --- /dev/null +++ b/src/Private/AuthenticateNPrinting.ps1 @@ -0,0 +1,25 @@ +function AuthenticateNPrinting { + param ( + [string]$AuthScheme, + [pscredential]$Credentials + ) + + $LoginUrl = if ($AuthScheme -eq 'NPrinting') { + "$($script:NPEnv.URLServerBase)/login" + } else { + "$($script:NPEnv.URLServerAPI)/login/$AuthScheme" + } + + Write-Verbose "Authenticating to $LoginUrl" + if ($AuthScheme -eq 'NPrinting' -and $Credentials) { + $Body = @{ + username = $Credentials.UserName + password = $Credentials.GetNetworkCredential().Password + } | ConvertTo-Json -Depth 3 + + return Invoke-NPRequest -Path $LoginUrl -method 'Post' -Data $Body + } + + return Invoke-NPRequest -Path $LoginUrl -method 'Get' +} + \ No newline at end of file diff --git a/src/Private/DecodeUnicodeEscapes.ps1 b/src/Private/DecodeUnicodeEscapes.ps1 new file mode 100644 index 0000000..73bd607 --- /dev/null +++ b/src/Private/DecodeUnicodeEscapes.ps1 @@ -0,0 +1,12 @@ +function DecodeUnicodeEscapes { + param ( + [string]$InputString + ) + + $decoded = [regex]::Replace($InputString, '\\u([0-9a-fA-F]{4})', { + param($match) + [char]::ConvertFromUtf32([int]::Parse($match.Groups[1].Value, [System.Globalization.NumberStyles]::HexNumber)) + }) + + return $decoded +} \ No newline at end of file diff --git a/src/Private/GetNPFilter.ps1 b/src/Private/GetNPFilter.ps1 new file mode 100644 index 0000000..e0141c8 --- /dev/null +++ b/src/Private/GetNPFilter.ps1 @@ -0,0 +1,28 @@ +function GetNPFilter { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true, HelpMessage = "The property to filter by.")] + [string]$Property, + + [Parameter(Mandatory = $true, HelpMessage = "The value to filter for.")] + [string]$Value, + + [Parameter(Mandatory = $true, HelpMessage = "The existing filter string.")] + [string]$Filter + ) + + # Process the property and value for filtering + if ($null -ne $Property -and $null -ne $Value) { + # Replace wildcard character `*` with `%` + $Value = $Value -replace '\*', '%' + + # Determine the query separator based on the current filter + $QuerySeparator = if ($Filter.StartsWith('?')) { '&' } else { '?' } + + # Append the new filter clause + $Filter = "$Filter$QuerySeparator$Property=$Value" + } + + # Return the updated filter string + return $Filter +} diff --git a/src/Private/GetXSRFToken.ps1 b/src/Private/GetXSRFToken.ps1 new file mode 100644 index 0000000..668278b --- /dev/null +++ b/src/Private/GetXSRFToken.ps1 @@ -0,0 +1,25 @@ + +function GetXSRFToken { + [CmdletBinding()] + param ( + [Parameter(HelpMessage = 'Return only the raw token value instead of the header dictionary.')] + [switch]$Raw + ) + + try { + # Retrieve the XSRF token from the cookies + $Token = $script:NPEnv.WebRequestSession.Cookies.GetCookies($script:NPEnv.URLServerBase) | Where-Object { $_.Name -eq 'NPWEBCONSOLE_XSRF-TOKEN' } + + # Return the raw token value or header dictionary based on the Raw parameter + if ($Raw) { + return $Token.Value + } else { + # Create a header dictionary with the token + $Header = [System.Collections.Generic.Dictionary[String, String]]::new() + $Header.Add('X-XSRF-TOKEN', $Token.Value) + return $Header + } + } catch { + Write-Error "Failed to retrieve XSRF token: $_" + } +} diff --git a/src/Private/SetTrustAllCertificates.ps1 b/src/Private/SetTrustAllCertificates.ps1 new file mode 100644 index 0000000..2f4b582 --- /dev/null +++ b/src/Private/SetTrustAllCertificates.ps1 @@ -0,0 +1,27 @@ + +function SetTrustAllCertificates { + if (-not ('CTrustAllCerts' -as [type])) { + Add-Type -TypeDefinition @' + using System; + using System.Net; + using System.Net.Security; + using System.Security.Cryptography.X509Certificates; + + public static class CTrustAllCerts { + public static bool ReturnTrue(object sender, + X509Certificate certificate, + X509Chain chain, + SslPolicyErrors sslPolicyErrors) { return true; } + + public static RemoteCertificateValidationCallback GetDelegate() { + return new RemoteCertificateValidationCallback(CTrustAllCerts.ReturnTrue); + } + } +'@ + Write-Verbose -Message 'Added Cert Ignore Type' + } + + [System.Net.ServicePointManager]::ServerCertificateValidationCallback = [CTrustAllCerts]::GetDelegate() + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + Write-Verbose -Message 'Server Certificate Validation Bypass' +} \ No newline at end of file diff --git a/src/Public/Connect-NPrinting.ps1 b/src/Public/Connect-NPrinting.ps1 new file mode 100644 index 0000000..bcf5b10 --- /dev/null +++ b/src/Public/Connect-NPrinting.ps1 @@ -0,0 +1,153 @@ +<# +.SYNOPSIS + Creates an authenticated session token. + +.DESCRIPTION + Connect-NPrinting initializes the `$Script:NPEnv` variable, + which is used for authenticated requests to the NPrinting server. + +.PARAMETER Prefix + The protocol prefix (http or https). + +.PARAMETER Computer + The NPrinting server hostname or IP address. + +.PARAMETER Port + The port to connect to (default: 4993). + +.PARAMETER Return + If specified, returns the authenticated user ID. + +.PARAMETER Credentials + The credentials to use for authentication. + +.PARAMETER TrustAllCertificates + If specified, bypasses SSL certificate validation. + +.PARAMETER AuthScheme + Authentication scheme (ntlm or NPrinting). + +.EXAMPLE + Connect-NPrinting -Computer 'nprinting-server' -Credentials (Get-Credential) + + This example connects to the NPrinting server using the specified credentials. + +.EXAMPLE + Connect-NPrinting -Computer 'nprinting-server' -TrustAllCertificates + + This example connects to the NPrinting server and bypasses SSL certificate validation. + +.NOTES + For more information, visit the NPrinting API documentation. + +#> +function Connect-NPrinting { + [CmdletBinding(DefaultParameterSetName = 'Default')] + param ( + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'Creds')] + [ValidateSet('http', 'https')] + [string]$Prefix = 'https', + + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'Creds')] + [string]$Computer = $env:COMPUTERNAME, + + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'Creds')] + [string]$Port = '4993', + + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'Creds')] + [switch]$Return, + + [Parameter(ParameterSetName = 'Creds', Mandatory = $true)] + [pscredential]$Credentials, + + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'Creds')] + [Alias('TrustAllCerts')] + [switch]$TrustAllCertificates, + + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'Creds')] + [ValidateSet('ntlm', 'NPrinting')] + [string]$AuthScheme = 'ntlm' + ) + + # Enforce dynamic validation for NPrinting AuthScheme + if ($AuthScheme -eq 'NPrinting' -and $PSCmdlet.ParameterSetName -ne 'Creds') { + throw "Credentials are mandatory when AuthScheme is set to 'NPrinting'. Please use the 'Creds' parameter set." + } + + # Define API paths + $APIPath = 'api' + $APIVersion = 'v1' + + # Trust all certificates if specified + if ($TrustAllCertificates) { + SetTrustAllCertificates + Write-Verbose 'TrustAllCertificates enabled' + } + + # Validate local service if connecting to localhost + if ($Computer -eq $env:COMPUTERNAME) { + try { + Get-Service -Name 'QlikNPrintingWebEngine' -ErrorAction Stop | Out-Null + } catch { + Write-Error "Local service 'QlikNPrintingWebEngine' is not running." + return + } + } + + # Parse Computer parameter for protocol and port + if ($Computer -match ':') { + if ($Computer -match '^http') { + $Prefix, $Computer = $Computer -split '://' + } + if ($Computer -match ':') { + $Computer, $Port = $Computer -split ':' + } + } + + # Initialize environment + $CookieContainer = New-Object System.Net.CookieContainer + # Construct base URL + $BaseURL = "$($Prefix)://$($Computer):$($Port)" + + # Initialize the NPEnv hash table + $script:NPEnv = @{ + TrustAllCertificates = $TrustAllCertificates + Prefix = $Prefix + Computer = $Computer + Port = $Port + API = $APIPath + APIVersion = $APIVersion + URLServerBase = $BaseURL + URLServerAPI = "$BaseURL/$APIPath/$APIVersion" + URLServerNPE = "$BaseURL/npe" + WebRequestSession = New-Object Microsoft.PowerShell.Commands.WebRequestSession + } + + $script:NPEnv.WebRequestSession.UserAgent = 'Windows' + $script:NPEnv.WebRequestSession.Cookies = $CookieContainer + + # Handle authentication based on parameter set + switch ($PSCmdlet.ParameterSetName) { + 'Default' { + $script:NPEnv.WebRequestSession.UseDefaultCredentials = $true + } + 'Creds' { + $script:NPEnv.WebRequestSession.Credentials = $Credentials + } + } + + # Authenticate and get token + $AuthToken = AuthenticateNPrinting -AuthScheme $AuthScheme -Credentials $Credentials + + # Return token if requested + if ($Return) { + return $AuthToken + } +} + diff --git a/src/Public/Get-NPConnections.ps1 b/src/Public/Get-NPConnections.ps1 new file mode 100644 index 0000000..c8e1e7b --- /dev/null +++ b/src/Public/Get-NPConnections.ps1 @@ -0,0 +1,31 @@ +<# +.SYNOPSIS + Retrieves the list of NPrinting connections. + +.DESCRIPTION + The Get-NPConnections function retrieves the list of connections from the NPrinting server. + It uses the Invoke-NPRequest function to send a GET request to the 'connections' endpoint of the NPrinting API. + +.EXAMPLE + Get-NPConnections + + This example retrieves all NPrinting connections. + +.NOTES + For more information, visit the NPrinting API documentation: + https://help.qlik.com/en-US/nprinting/February2024/APIs/NP+API/index.html?page=19#Connections + +.LINK + https://help.qlik.com/en-US/nprinting/February2024/APIs/NP+API/index.html?page=19#Connections +#> +function Get-NPConnections { + [CmdletBinding()] + param() + try { + # Retrieve the NPrinting Connections + $NPConnections = Invoke-NPRequest -Path 'connections' -Method Get + return $NPConnections + } catch { + Write-Error "Failed to retrieve NPrinting connections: $_" + } +} \ No newline at end of file diff --git a/src/Public/Get-NPFilters.ps1 b/src/Public/Get-NPFilters.ps1 new file mode 100644 index 0000000..08282f8 --- /dev/null +++ b/src/Public/Get-NPFilters.ps1 @@ -0,0 +1,33 @@ +<# +.SYNOPSIS + Retrieves the list of NPrinting filters. + +.DESCRIPTION + The Get-NPFilters function retrieves the list of filters from the NPrinting server. + It uses the Invoke-NPRequest function to send a GET request to the 'filters' endpoint of the NPrinting API. + +.EXAMPLE + Get-NPFilters + + This example retrieves all NPrinting filters. + +.NOTES + For more information, visit the NPrinting API documentation: + https://help.qlik.com/en-US/nprinting/February2024/APIs/NP+API/index.html?page=26 + +.LINK + https://help.qlik.com/en-US/nprinting/February2024/APIs/NP+API/index.html?page=26 +#> +function Get-NPFilters { + [CmdletBinding()] + param ( + ) + + try { + # Retrieve and update the global NPFilters variable + $NPFilters = Invoke-NPRequest -Path 'filters' -Method Get + return $NPFilters + } catch { + Write-Error "Failed to retrieve NPFilters: $_" + } +} \ No newline at end of file diff --git a/src/Public/Get-NPGroups.ps1 b/src/Public/Get-NPGroups.ps1 new file mode 100644 index 0000000..39f3077 --- /dev/null +++ b/src/Public/Get-NPGroups.ps1 @@ -0,0 +1,62 @@ +<# +.SYNOPSIS + Retrieves the list of NPrinting groups. + +.DESCRIPTION + The Get-NPGroups function retrieves the list of groups from the NPrinting server. + It uses the Invoke-NPRequest function to send a GET request to the 'groups' endpoint of the NPrinting API. + Optional parameters can be specified to filter the results. + +.PARAMETER Limit + Specifies the maximum number of groups to retrieve. + +.PARAMETER Offset + Specifies the number of groups to skip before starting to return results. + +.EXAMPLE + Get-NPGroups + + This example retrieves all NPrinting groups. + +.EXAMPLE + Get-NPGroups -Limit 10 + + This example retrieves a maximum of 10 NPrinting groups. + +.EXAMPLE + Get-NPGroups -Limit 10 -Offset 5 + + This example retrieves a maximum of 10 NPrinting groups, skipping the first 5. + +.NOTES + For more information, visit the NPrinting API documentation: + https://help.qlik.com/en-US/nprinting/February2024/APIs/NP+API/index.html?page=32 + +.LINK + https://help.qlik.com/en-US/nprinting/February2024/APIs/NP+API/index.html?page=32 +#> +function Get-NPGroups { + [CmdletBinding()] + param ( + [Parameter(HelpMessage = "Specifies the maximum number of groups to retrieve.")] + [int32]$Limit, + [Parameter(HelpMessage = "Specifies the number of groups to skip before starting to return results.")] + [int32]$Offset + ) + + try { + $Filter = "" + if ($PSBoundParameters.ContainsKey('Limit')) { + $Filter = GetNPFilter -Filter $Filter -Property "limit" -Value $Limit.ToString() + } + if ($PSBoundParameters.ContainsKey('Offset')) { + $Filter = GetNPFilter -Filter $Filter -Property "offset" -Value $Offset.ToString() + } + + # Retrieve the NPrinting Groups + $NPGroups = Invoke-NPRequest -Path "groups$Filter" -Method Get + return $NPGroups + } catch { + Write-Error "Failed to retrieve NPrinting groups: $_" + } +} \ No newline at end of file diff --git a/src/Public/Get-NPRoles.ps1 b/src/Public/Get-NPRoles.ps1 new file mode 100644 index 0000000..e0df816 --- /dev/null +++ b/src/Public/Get-NPRoles.ps1 @@ -0,0 +1,96 @@ +<# +.SYNOPSIS + Retrieves the list of NPrinting roles. + +.DESCRIPTION + The Get-NPRoles function retrieves the list of roles from the NPrinting server. + It uses the Invoke-NPRequest function to send a GET request to the 'roles' endpoint of the NPrinting API. + Optional parameters can be specified to filter the results. + +.PARAMETER appId + Specifies the ID of the app to filter the roles. + +.PARAMETER roleName + Specifies the name of the role to filter the results. + +.PARAMETER enabled + Specifies whether to filter the roles by their enabled status. + +.PARAMETER offset + Specifies the number of roles to skip before starting to return results. + +.PARAMETER limit + Specifies the maximum number of roles to retrieve. + +.EXAMPLE + Get-NPRoles + + This example retrieves all NPrinting roles. + +.EXAMPLE + Get-NPRoles -appId "12345" + + This example retrieves all NPrinting roles for the specified app ID. + +.EXAMPLE + Get-NPRoles -roleName "Admin" + + This example retrieves all NPrinting roles with the name "Admin". + +.EXAMPLE + Get-NPRoles -enabled $true + + This example retrieves all enabled NPrinting roles. + +.EXAMPLE + Get-NPRoles -limit 10 -offset 5 + + This example retrieves a maximum of 10 NPrinting roles, skipping the first 5. + +.NOTES + For more information, visit the NPrinting API documentation: + https://help.qlik.com/en-US/nprinting/February2024/APIs/NP+API/index.html?page=50 + +.LINK + https://help.qlik.com/en-US/nprinting/February2024/APIs/NP+API/index.html?page=50 +#> +function Get-NPRoles { + [CmdletBinding()] + param ( + [Parameter(HelpMessage = 'Specifies the number of roles to skip before starting to return results.')] + [int32]$offset, + [Parameter(HelpMessage = 'Specifies the maximum number of roles to retrieve.')] + [int32]$limit, + [Parameter(HelpMessage = 'Specifies the ID of the app to filter the roles.')] + [string]$appId, + [Parameter(HelpMessage = 'Specifies the name of the role to filter the results.')] + [string]$roleName, + [Parameter(HelpMessage = 'Specifies whether to filter the roles by their enabled status.')] + [bool]$enabled + ) + + try { + $Filter = '' + if ($PSBoundParameters.ContainsKey('appId')) { + $Filter = GetNPFilter -Filter $Filter -Property 'appId' -Value $appId + } + if ($PSBoundParameters.ContainsKey('roleName')) { + $Filter = GetNPFilter -Filter $Filter -Property 'roleName' -Value $roleName + } + if ($PSBoundParameters.ContainsKey('enabled')) { + $Filter = GetNPFilter -Filter $Filter -Property 'enabled' -Value $enabled.ToString() + } + if ($PSBoundParameters.ContainsKey('offset')) { + $Filter = GetNPFilter -Filter $Filter -Property 'offset' -Value $offset.ToString() + } + if ($PSBoundParameters.ContainsKey('limit')) { + $Filter = GetNPFilter -Filter $Filter -Property 'limit' -Value $limit.ToString() + } + + # Retrieve the NPrinting Roles + $NPRoles = Invoke-NPRequest -Path "roles$Filter" -method Get + return $NPRoles + } catch { + Write-Error "Failed to retrieve NPRoles: $_" + } +} \ No newline at end of file diff --git a/src/Public/Get-NPTasks.ps1 b/src/Public/Get-NPTasks.ps1 new file mode 100644 index 0000000..377aee1 --- /dev/null +++ b/src/Public/Get-NPTasks.ps1 @@ -0,0 +1,93 @@ +<# +.SYNOPSIS + Retrieves the list of NPrinting tasks. + +.DESCRIPTION + The Get-NPTasks function retrieves the list of tasks from the NPrinting server. + It uses the Invoke-NPRequest function to send a GET request to the 'tasks' endpoint of the NPrinting API. + Optional parameters can be specified to filter the results. + +.PARAMETER ID + Specifies the ID of the task to retrieve. + +.PARAMETER Name + Specifies the name of the task to filter the results. + +.PARAMETER Executions + Specifies whether to include execution details for each task. + +.EXAMPLE + Get-NPTasks + + This example retrieves all NPrinting tasks. + +.EXAMPLE + Get-NPTasks -ID "12345" + + This example retrieves the NPrinting task with the specified ID. + +.EXAMPLE + Get-NPTasks -Name "Monthly Report" + + This example retrieves all NPrinting tasks with the name "Monthly Report". + +.EXAMPLE + Get-NPTasks -Executions + + This example retrieves all NPrinting tasks and includes execution details for each task. + +.NOTES + For more information, visit the NPrinting API documentation: + https://help.qlik.com/en-US/nprinting/February2024/APIs/NP+API/index.html?page=60 + +.LINK + https://help.qlik.com/en-US/nprinting/February2024/APIs/NP+API/index.html?page=60 +#> +function Get-NPTasks { + [CmdletBinding()] + param ( + [Parameter(HelpMessage = "Specifies the ID of the task to retrieve.")] + [string]$ID, + [Parameter(HelpMessage = "Specifies the name of the task to filter the results.")] + [string]$Name, + [Parameter(HelpMessage = "Specifies whether to include execution details for each task.")] + [switch]$Executions + ) + + try { + # Construct the base path + $BasePath = 'tasks' + $Path = if ($ID) { "$BasePath/$ID" } else { $BasePath } + + # Add filter by name if specified + if ($Name) { + $Path = "$Path?filter=name eq '$Name'" + } + + Write-Verbose "Request Path: $Path" + + # Fetch tasks from the API + $NPTasks = Invoke-NPRequest -Path $Path -method Get + + # Include execution details if requested + if ($Executions) { + $NPTasks | ForEach-Object { + try { + $ExecutionPath = "tasks/$($_.id)/Executions" + Write-Verbose "Fetching executions for task ID: $($_.id)" + $NPTaskExecutions = Invoke-NPRequest -Path $ExecutionPath -method Get + + # Add executions as a NoteProperty to the task object + Add-Member -InputObject $_ -MemberType NoteProperty -Name 'Executions' -Value $NPTaskExecutions -Force + } catch { + Write-Warning "Failed to fetch executions for task ID: $($_.id): $_" + } + } + } + + return $NPTasks + + } catch { + Write-Error "Failed to retrieve NPTasks: $_" + } +} \ No newline at end of file diff --git a/src/Public/Invoke-NPRequest.ps1 b/src/Public/Invoke-NPRequest.ps1 new file mode 100644 index 0000000..dbc04c6 --- /dev/null +++ b/src/Public/Invoke-NPRequest.ps1 @@ -0,0 +1,186 @@ +<# +.SYNOPSIS + Sends a request to the NPrinting API. + +.DESCRIPTION + The Invoke-NPRequest function sends a request to the NPrinting API. + It supports various HTTP methods and can handle different types of requests, including those for NPrinting Private Endpoints (NPE). + +.PARAMETER Path + Specifies the API endpoint path. + +.PARAMETER Method + Specifies the HTTP method to use for the request. Valid values are 'Get', 'Post', 'Patch', 'Delete', and 'Put'. + +.PARAMETER Data + Specifies the data to send with the request, if applicable. + +.PARAMETER NPE + Indicates that the request is for NPrinting Private Endpoints. + +.PARAMETER Count + Specifies the number of items to retrieve for NPE requests. + +.PARAMETER OrderBy + Specifies the field by which to order the results for NPE requests. + +.PARAMETER Page + Specifies the page number to retrieve for NPE requests. + +.PARAMETER OutFile + Specifies the file to which the response should be written for download requests. + +.PARAMETER InFile + Specifies the file to upload for upload requests. + +.EXAMPLE + Invoke-NPRequest -Path 'connections' -Method Get + + This example sends a GET request to the 'connections' endpoint of the NPrinting API. + +.EXAMPLE + Invoke-NPRequest -Path 'reports' -Method Post -Data $reportData + + This example sends a POST request to the 'reports' endpoint of the NPrinting API with the specified data. + +.EXAMPLE + Invoke-NPRequest -Path 'tasks' -Method Get -NPE -Count 10 -OrderBy 'Name' -Page 2 + + This example sends a GET request to the 'tasks' endpoint of the NPrinting API for NPrinting Private Endpoints, retrieving 10 items ordered by 'Name' on page 2. + +.NOTES + For more information, visit the NPrinting API documentation. + +#> +function Invoke-NPRequest { + param ( + [Parameter(Mandatory = $true, Position = 0)] + [string]$Path, + + [ValidateSet('Get', 'Post', 'Patch', 'Delete', 'Put')] + [string]$Method = 'Get', + + $Data, + + [Parameter(ParameterSetName = 'NPE')] + [Parameter(ParameterSetName = 'NPEDownload')] + [Parameter(ParameterSetName = 'NPEUpload')] + [switch]$NPE, + + [Parameter(ParameterSetName = 'NPE')] + [Parameter(ParameterSetName = 'NPEDownload')] + [Parameter(ParameterSetName = 'NPEUpload')] + [int]$Count = -1, + + [Parameter(ParameterSetName = 'NPE')] + [Parameter(ParameterSetName = 'NPEDownload')] + [Parameter(ParameterSetName = 'NPEUpload')] + [string]$OrderBy = 'Name', + + [Parameter(ParameterSetName = 'NPE')] + [Parameter(ParameterSetName = 'NPEDownload')] + [Parameter(ParameterSetName = 'NPEUpload')] + [int]$Page = 1, + + [Parameter(ParameterSetName = 'NPEDownload', Mandatory = $true)] + [System.IO.FileInfo]$OutFile, + + [Parameter(ParameterSetName = 'NPEUpload', Mandatory = $true)] + [System.IO.FileInfo]$InFile + ) + + $NPEnv = $script:NPEnv + if (-not $NPEnv) { + Write-Warning 'Attempting to establish Default connection' + Connect-NPrinting + } + + # Build query parameters + $QueryParameters = @{} + if ($NPE -and (-not $PSBoundParameters.ContainsKey('Infile') -and $PSBoundParameters.ContainsKey('Outfile'))) { + if ($Count -ne -1) { $QueryParameters['count'] = $Count } + if ($OrderBy) { $QueryParameters['orderBy'] = $OrderBy } + $QueryParameters['page'] = $Page + } + + # Build URI + $URI = BuildNPURI -Path $Path -NPE:$NPE -URLServerAPI $NPEnv.URLServerAPI -URLServerNPE $NPEnv.URLServerNPE -QueryParameters $QueryParameters + + # Splat for Invoke-RestMethod + $SplatRest = @{ + URI = $URI + WebSession = $NPEnv.WebRequestSession + Method = $Method + ContentType = 'application/json;charset=UTF-8' + Headers = GetXSRFToken + } + + # Add credentials if session is invalid + if ([string]::IsNullOrEmpty($NPEnv.WebRequestSession.Cookies.GetCookies($NPEnv.URLServerAPI)) -and $NPEnv.Credentials) { + $SplatRest['Credential'] = $NPEnv.Credentials + } + + # Add JSON body if data exists + if ($Data) { + $JsonData = if ($Data -is [string]) { $Data } else { ConvertTo-Json -InputObject $Data -Depth 5 } + $SplatRest['Body'] = $JsonData + } + + # Handle file upload/download + if ($OutFile) { $SplatRest['OutFile'] = $OutFile } + if ($InFile) { + # + if ($InFile.Extension -eq '.zip') { + # Define the boundary + $boundary = '-----------------------------' + [System.Guid]::NewGuid().ToString('N') + + # Create the Content-Type header with the boundary + $SplatRest.contentType = "multipart/form-data; boundary=$boundary" + + # Read the file content as bytes + $fileBytes = [System.IO.File]::ReadAllBytes($InFile.FullName) + + # Convert the bytes to a Base64 string + $fileContent = [System.Text.Encoding]::GetEncoding('ISO-8859-1').GetString($fileBytes) + + # Now construct your body + $body = @" +--$boundary +Content-Disposition: form-data; name="file"; filename="$($InFile.Name)" +Content-Type: application/x-zip-compressed + +$fileContent +--$boundary-- +"@ + $SplatRest['Body'] = $body + } else { + $SplatRest['InFile'] = $InFile + } + } + + # Debug output + if ($PSBoundParameters.Debug.IsPresent) { + Write-Warning "Debug: $($SplatRest | Out-String)" + } + + # Invoke the request + try { + $Result = Invoke-RestMethod @SplatRest + } catch { + Write-Warning "Error during REST call: $($_.Exception.Message)" + Write-Warning "From: $($_.Exception.Response.ResponseUri.AbsoluteUri) `nResponse: $($_.Exception.Response.StatusDescription)" + return $_ + } + + # Handle the result + if ($OutFile) { return } + elseif ($Result) { + if ($NPE -and $Result.result) { return $Result.result } + if ($Result.data) { + if ($Result.data.items) { return $Result.data.items } else { return $Result.data } + } + return $Result + } else { + Write-Error 'No results received' + } +} \ No newline at end of file diff --git a/src/Resources/BuildFiles/Scripts/DynamicLoading.ps1 b/src/Resources/BuildFiles/Scripts/DynamicLoading.ps1 new file mode 100644 index 0000000..ce656aa --- /dev/null +++ b/src/Resources/BuildFiles/Scripts/DynamicLoading.ps1 @@ -0,0 +1,35 @@ +$DynamicLoading = @' +[System.IO.DirectoryInfo]$modulePath = $PSScriptRoot +[System.IO.DirectoryInfo]$publicFunctionsPath = Join-Path $modulePath -ChildPath 'Public' +[System.IO.DirectoryInfo]$privateFunctionsPath = Join-Path $modulePath -ChildPath 'Private' +[System.IO.DirectoryInfo]$classesPath = Join-Path $modulePath -ChildPath 'Classes' +Write-Warning "$PSScriptRoot" +$aliases = @() +[regex]$FunctionName = [regex]::new('(?<=function )([\w]+-[\w]+)(?>[\s]+\{)', [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) + +if ($publicFunctionsPath.Exists) { + $publicFunctions = Get-ChildItem -Path $publicFunctionsPath.FullName | Where-Object { $_.Extension -eq '.ps1' } + $publicFunctions | ForEach-Object { . $_.FullName } + $publicFunctions | ForEach-Object { # Export all of the public functions from this module + $content = Get-Content $_.FullName + $functions = $FunctionName.Matches($($content)).ForEach({ $_.Groups[1].value }) | Sort-Object -Unique + foreach ($function in $functions) { + # The command has already been sourced in above. Query any defined aliases. + $alias = Get-Alias -Definition $function -ErrorAction SilentlyContinue + if ($alias) { + $aliases += $alias + Export-ModuleMember -Function $function -Alias $alias -Verbose + } else { + Export-ModuleMember -Function "$($function)" -Verbose + } + } + } +} +if ($privateFunctionsPath.Exists) { + Get-ChildItem -Path $privateFunctionsPath.FullName | Where-Object { $_.Extension -eq '.ps1' } | ForEach-Object { . $_.FullName } +} +if ($classesPath.Exists) { + Get-ChildItem -Path $classesPath.FullName | Where-Object { $_.Extension -eq '.ps1' } | ForEach-Object { . $_.FullName } +} +'@ +return $DynamicLoading \ No newline at end of file diff --git a/src/Resources/BuildFiles/Scripts/ModuleVersion.ps1 b/src/Resources/BuildFiles/Scripts/ModuleVersion.ps1 new file mode 100644 index 0000000..0edab4b --- /dev/null +++ b/src/Resources/BuildFiles/Scripts/ModuleVersion.ps1 @@ -0,0 +1,37 @@ +function Get-ModuleVersion { + param ( + [Parameter(Mandatory = $false)] + [PSCustomObject]$Config, + [int]$Major, + [int]$Minor, + [int]$Build, + [int]$Revision + ) + + # Set the Version + if ($null -ne $Config.ModuleManifest.ModuleVersion) { + $ModuleVersion = $Config.ModuleManifest.ModuleVersion + } else { + try { + $Tag = $(git tag -l --sort=refname v* | Select-Object -Last 1) + } catch { + $Tag = 'v0.0.0.0' + } + if ($null -eq $Tag) { + $Tag = 'v0.0.0.0' + } + try { + $Version = [version]::new(($Tag).substring(1)) + } catch { + $Version = [version]::new('0.0.0.0') + } + $Major = if ($PSBoundParameters.ContainsKey('Major')) { $Major } else { $Version.Major } + $Minor = if ($PSBoundParameters.ContainsKey('Minor')) { $Minor } else { $Version.Minor } + $Year = [datetime]::UtcNow.Year + $DayOfYear = [datetime]::UtcNow.DayOfYear + $Build = if ($PSBoundParameters.ContainsKey('Build')) { $Build } else { '{0:D2}{1:D3}' -f ($Year % 100), $DayOfYear } + $Revision = if ($PSBoundParameters.ContainsKey('Revision')) { $Revision } else { [datetime]::UtcNow.TimeOfDay.TotalSeconds.ToString('#')/2 } + $ModuleVersion = [version]::new("$Major.$Minor.$Build.$Revision") + } + return $ModuleVersion +} \ No newline at end of file diff --git a/src/Resources/BuildModule.ps1 b/src/Resources/BuildModule.ps1 new file mode 100644 index 0000000..8dcb2a2 --- /dev/null +++ b/src/Resources/BuildModule.ps1 @@ -0,0 +1,333 @@ +<# + .NOTES + =========================================================================== + Created on: 2022-11-30 10:44 PM + Created by: Marc Collins + Filename: BuildModule.ps1 + Version: 1.0.2022.1130 + =========================================================================== + .DESCRIPTION + This uses a Environment variable `GH_TOKEN` to connect to GitHub to pull information about the Reporitory, + The Module Manifest informaiton will be automatically generated by this script. + All setting can be hardcoded in the Module_Config.json if necessary. + Files placed in the SRC subfolder will be compiled into the Module +#> + +[CmdletBinding()] +param +( + [system.IO.DirectoryInfo]$BuildRoot = $(Get-Item $PWD), + [switch]$ExportAll +) + +$DirSeparator = $([system.io.path]::DirectorySeparatorChar) +$BinDIR = 'bin' +[system.IO.FileInfo]$ConfigFile = "$($BuildRoot.FullName)$($DirSeparator)Module_Config.json" +if ($ConfigFile.Exists) { + $Config = Get-Content "$($BuildRoot.FullName)$($DirSeparator)Module_Config.json" | ConvertFrom-Json +} else { + $Config = [ordered]@{ RepositoryURI = 'https://localhost'; ModuleName = "$($BuildRoot.Name)"; ModuleManifest = @{ Author = $null; CompanyName = $null; Copyright = $null; Description = $null; DotNetFrameworkVersion = $null; FileList = $null; FunctionsToExport = $null; Guid = $null; HelpInfoUri = $null; LicenseUri = $null; ModuleVersion = $null; PrivateData = $null; ProjectUri = $null; ReleaseNotes = $null; Tags = $null; } } + ConvertTo-Json $Config | Out-File -Encoding utf8 -FilePath $ConfigFile.FullName +} + +$paramNewModuleManifest = @{ + Guid = [guid]::NewGuid() +} + +if ($Null -eq $Env:GH_TOKEN) { + $RequiredValues = $true + Write-Warning "This build is primariy designed to run as a GitHub Action: Ensure `$Env:GH_Token is set" + if ($null -eq $Config.RepositoryURI) { + Write-Warning 'You must specify the RepositoryURI in the Config.json' + $RequiredValues = $false + } else { + $RepositoryURI = $Config.RepositoryURI + } + + if ($null -eq $Config.ModuleName) { + Write-Warning 'You must specify the ModuleName in the Config.json' + $RequiredValues = $false + } else { + $ModuleName = $Config.ModuleName + } + + if ($RequiredValues -eq $false) { + return + } +} else { + $GH_TOKEN = $Env:GH_TOKEN + $RepositoryURI = "$($Env:GITHUB_API_URL)/repos/$($Env:GITHUB_REPOSITORY)" + $RepositoryInfo = Invoke-RestMethod $RepositoryURI -ContentType 'application/json' -Headers @{ + Authorization = "Bearer $GH_TOKEN" + } + $RepositoryOwnerInfo = Invoke-RestMethod $RepositoryInfo.owner.url -ContentType 'application/json' -Headers @{ + Authorization = "Bearer $GH_TOKEN" + } + + $Author = $RepositoryOwnerInfo.name + if ($Null -ne $RepositoryOwnerInfo.company) { + $Company = $RepositoryOwnerInfo.company + } else { + $Company = $RepositoryOwnerInfo.login + } + $ModuleName = $RepositoryInfo.name +} + +if ($Null -ne $Config.ModuleManifest.Author) { + $paramNewModuleManifest.Author = $Config.ModuleManifest.Author +} elseif ($Null -ne $RepositoryInfo) { + $paramNewModuleManifest.Author = $Author +} + +if ($Null -ne $Config.ModuleManifest.CompanyName) { + $paramNewModuleManifest.CompanyName = $Config.ModuleManifest.CompanyName +} elseif ($Null -ne $RepositoryInfo) { + $paramNewModuleManifest.CompanyName = $Company +} + +if ($Null -ne $Config.ModuleManifest.Copyright) { + $paramNewModuleManifest.Copyright = $Config.ModuleManifest.Copyright +} + +if ($Null -ne $Config.ModuleManifest.Description) { + $paramNewModuleManifest.Description = $Config.ModuleManifest.Description +} elseif ($Null -ne $RepositoryInfo) { + $paramNewModuleManifest.Description = $RepositoryInfo.Description +} + +if ($Null -ne $Config.ModuleManifest.DotNetFrameworkVersion) { + $paramNewModuleManifest.DotNetFrameworkVersion = $Config.ModuleManifest.DotNetFrameworkVersion +} + +if ($Null -ne $Config.ModuleManifest.FileList) { + $paramNewModuleManifest.FileList = $Config.ModuleManifest.FileList +} + +if ($Null -ne $Config.ModuleManifest.Guid) { + $paramNewModuleManifest.Guid = $Config.ModuleManifest.Guid +} elseif ($Null -ne $Env:GITHUB_SHA) { + $paramNewModuleManifest.Guid = [guid]::Parse($Env:GITHUB_SHA.Substring(0, 32)) +} + +if ($Null -ne $Config.ModuleManifest.HelpInfoUri) { + $paramNewModuleManifest.HelpInfoUri = $Config.ModuleManifest.HelpInfoUri +} elseif ($Null -ne $RepositoryInfo) { + if ($Null -ne $RepositoryInfo.homepage) { + $paramNewModuleManifest.HelpInfoUri = $RepositoryInfo.homepage + } elseif ($RepositoryInfo.has_issues) { + $paramNewModuleManifest.HelpInfoUri = "$($RepositoryInfo.html_url)/issues" + } else { + $paramNewModuleManifest.HelpInfoUri = $RepositoryInfo.html_url + } +} + +if ($Null -ne $Config.ModuleManifest.LicenseUri) { + $paramNewModuleManifest.LicenseUri = $Config.ModuleManifest.LicenseUri +} elseif ($Null -ne $RepositoryInfo) { + $paramNewModuleManifest.LicenseUri = "$($RepositoryInfo.html_url)/blob/$($env:GITHUB_REF_NAME)/LICENSE" +} + +if ($Null -ne $Config.ModuleManifest.PrivateData) { + $paramNewModuleManifest.PrivateData = $Config.ModuleManifest.PrivateData +} + +if ($Null -ne $Config.ModuleManifest.ProjectUri) { + $paramNewModuleManifest.ProjectUri = $Config.ModuleManifest.ProjectUri +} elseif ($Null -ne $RepositoryInfo) { + $paramNewModuleManifest.ProjectUri = $RepositoryInfo.html_url +} + +if ($Null -ne $Config.ModuleManifest.Tags) { + $paramNewModuleManifest.Tags = $Config.ModuleManifest.Tags +} elseif ($Null -ne $RepositoryInfo) { + # $paramNewModuleManifest.Tags = $RepositoryInfo.Tags ### CheckTHIS +} + +##################################################################### + +#Set the Version +if ($Null -ne $Config.ModuleManifest.ModuleVersion) { + $Version = [version]::new($Config.ModuleManifest.ModuleVersion) +} else { + try { + $Tag = $(git tag -l --sort=refname v* | Select-Object -Last 1) + } catch { + } + if ($Null -eq $Tag) { + $Tag = 'v0.0.0.0' + } + try { + $Version = [version]::new(($Tag).substring(1)); + } catch { + $Version = [version]::new('0.0.0.0'); + } +} +$ModuleVersion = [version]::new("$($Version.Major).$($Version.Minor).$("$([datetime]::utcnow.ToString('yy'))$([datetime]::UtcNow.DayOfYear.ToString('000'))").$([datetime]::UtcNow.TimeOfDay.TotalSeconds.ToString('#'))") + +if ($Null -ne $Config.ModuleManifest.ReleaseNotes) { + $paramNewModuleManifest.ReleaseNotes = $Config.ModuleManifest.ReleaseNotes +} elseif ($Null -ne $RepositoryInfo) { + $paramNewModuleManifest.ReleaseNotes = "$($RepositoryInfo.html_url)/releases/tag/v$($ModuleVersion)" +} + +$paramNewModuleManifest.ModuleVersion = $ModuleVersion +$RootModule = "$($ModuleName).psm1" +$ModuleManifest = "$($ModuleName).psd1" +$paramNewModuleManifest.path = $ModuleManifest +$paramNewModuleManifest.RootModule = $RootModule +$SRCDir = Join-Path $BuildRoot src +$ScriptFiles = Get-ChildItem $SRCDir -Recurse -Include '*.ps1' | Where-Object { + -not ($_.FullName -match '\\bin\\' -or $_.FullName -match '\\Resources\\' -or $_.Name -like 'Build-*') +} | Sort-Object -Property DirectoryName,BaseName + +$paramNewModuleManifest.FileList = $ScriptFiles| ForEach-Object { +join-path $_.Directory.name $_.Name +} + +$ModuleDIR = $Null +[System.io.DirectoryInfo]$ModuleDir = "$($BuildRoot.FullName)$($DirSeparator)$ModuleName" +$ModuleDIR.Refresh() +if ($ModuleDIR.Exists) { + Write-Host "Clearing Existing DIR: $($ModuleDIR.FullName)" + try { + $ModuleDir.Delete($true) + } catch { + Write-Error 'Module DIR exists and files are locked' + return $Null + } + + Start-Sleep -Seconds 1 + $ModuleDIR.Refresh() +} +$ModuleDir.Create() +$ModuleDIR.Refresh() +$ModuleZip = [System.IO.FileInfo]::new("$($BuildRoot.FullName)$($DirSeparator)$($ModuleName).zip").FullName + +$ScriptContent = foreach ($ScriptFile in $ScriptFiles) { + Get-Content $ScriptFile.FullName +} + +if ($Null -ne $Config.AllowDynamicContent -and $Config.AllowDynamicContent -eq $true) { + $moduleContent = . (Join-Path -Path $SRCDir -ChildPath "Resources/BuildFiles/Scripts/DynamicLoading.ps1") + Get-ChildItem $SRCDir -Directory -Exclude Resources | Where-Object { $_.GetFiles().Count -gt 0 } | Copy-Item -Destination $ModuleDIR -Recurse -Force +} else { + $moduleContent = $ScriptContent +} + +[system.io.fileInfo]$RMMD = Join-Path $SRCDir -ChildPath 'ReadMe.md' +if (-not $RMMD.Exists){ + [system.io.fileInfo]$RRMMD = Join-Path $BuildRoot -ChildPath 'ReadMe.md' + if($RRMMD.Exists){ + Copy-Item $RRMMD.FullName -Destination $(Join-Path $ModuleDIR -ChildPath 'ReadMe.md') -Force + } +} +$MDs = Get-ChildItem $SRCDir -Filter '*.md' +foreach ($MD in $MDs) { + if ($MD.Exists) { + Copy-Item -Path $MD.FullName -Destination "$($ModuleDIR.FullName)$($DirSeparator)" + } +} + +$BinPath = "$($BuildRoot.FullName)$($DirSeparator)src$($DirSeparator)$($BinDIR)" + +if (-not [string]::IsNullOrEmpty($BinPath)) { + $Directories = Get-ChildItem $BinPath -Recurse -Directory + $Modules = $Directories | Where-Object { + $_.GetFiles('*.psd1').Count -gt 0 + } + $Assemblies = $Directories | Where-Object { + $_.GetFiles('*.psd1').Count -eq 0 + } + + if ($Modules.count -gt 0) { + $NestedModules = $Modules.GetFiles('*.psd1').FullName.substring($BinPath.length - $BinDIR.length) + $Modules | ForEach-Object { + #"$($ModuleDir.FullName)$($_.FullName.Substring($BinPath.length - $BinDIR.length - 1))" + Copy-Item -Recurse -Path $_.FullName -Destination "$($ModuleDir.FullName)$($_.FullName.Substring($BinPath.length - $BinDIR.length - 1))" + } + $paramNewModuleManifest.NestedModules = $NestedModules + $paramNewModuleManifest.ModuleList = $NestedModules + } + + if ($Assemblies.count -gt 0) { + $RequiredDLLs = $Assemblies.GetFiles('*.dll').FullName.substring($BinPath.length - $BinDIR.length) + $Assemblies.GetFiles('*.dll') | ForEach-Object { + [system.io.fileInfo]$destfile = "$($ModuleDir.FullName)$($_.FullName.Substring($BinPath.length - $BinDIR.length - 1))" + $null = $destfile.Directory.Create() + Copy-Item -Recurse -Path $_.FullName -Destination "$($ModuleDir.FullName)$($_.FullName.Substring($BinPath.length - $BinDIR.length - 1))" + } + $paramNewModuleManifest.RequiredAssemblies = $RequiredDLLs + } +} + +Push-Location $ModuleDir.FullName +$moduleContent| Out-File $RootModule -Encoding utf8 + +if ($Null -ne $Config.ModuleManifest.FunctionsToExport) { + $paramNewModuleManifest.FunctionsToExport = $Config.ModuleManifest.FunctionsToExport +} else { + [regex]$FunctionName = [regex]::new('(?<=function )([\w]+-[\w]+)(?>[\s]+\{)', [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) + if ($ExportAll) { + [regex]$FunctionName = [regex]::new('(?<=[\s]?function )([\w]+-?[\w]+)(?>[\s]+\{)', [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) + } + $paramNewModuleManifest.FunctionsToExport = $FunctionName.Matches($ScriptContent).ForEach({ $_.Groups[1].value }) | Sort-Object -Unique +} + +if ($Null -ne $Config.ModuleManifest.CmdletsToExport) { + $paramNewModuleManifest.CmdletsToExport = $Config.ModuleManifest.CmdletsToExport +} else { + $paramNewModuleManifest.CmdletsToExport = @() +} + +if ($Null -ne $Config.ModuleManifest.VariablesToExport) { + $paramNewModuleManifest.VariablesToExport = $Config.ModuleManifest.VariablesToExport +} else { + $paramNewModuleManifest.VariablesToExport = @() +} + +if ($Null -ne $Config.ModuleManifest.AliasesToExport) { + $paramNewModuleManifest.AliasesToExport = $Config.ModuleManifest.AliasesToExport +} else { + $paramNewModuleManifest.AliasesToExport = @() +} + +#Write-Warning $($paramNewModuleManifest | ConvertTo-Json) +New-ModuleManifest @paramNewModuleManifest +Pop-Location +Compress-Archive -Path $ModuleDir -DestinationPath $ModuleZip -CompressionLevel Optimal -Force + +"Version=v$($ModuleVersion)" >> $ENV:GITHUB_OUTPUT +"ModuleName=$($ModuleName)" >> $ENV:GITHUB_OUTPUT +"ModuleDir=$($ModuleDir.FullName)" >> $ENV:GITHUB_OUTPUT +"ModuleDirRelative=$(Resolve-Path -Relative $ModuleDir.FullName)" >> $ENV:GITHUB_OUTPUT +"ModuleZip=$($ModuleZip)" >> $ENV:GITHUB_OUTPUT + +'### Finished! :rocket:' >> $env:GITHUB_STEP_SUMMARY +"#### Built ``$($ModuleName)``" >> $env:GITHUB_STEP_SUMMARY +"#### Version ``$($ModuleVersion)``" >> $env:GITHUB_STEP_SUMMARY +if ($($paramNewModuleManifest.FunctionsToExport | Measure-Object).count -gt 0) { + '##### Functions Included' >> $env:GITHUB_STEP_SUMMARY + ($paramNewModuleManifest.FunctionsToExport | ForEach-Object { + "- ``$($_)``" + }) >> $env:GITHUB_STEP_SUMMARY +} + +if ($($paramNewModuleManifest.VariablesToExport | Measure-Object).count -gt 0) { + '##### Variables Included' >> $env:GITHUB_STEP_SUMMARY + ($paramNewModuleManifest.VariablesToExport | ForEach-Object { + "- ``$($_)``" + }) >> $env:GITHUB_STEP_SUMMARY +} + +if ($($paramNewModuleManifest.CmdletsToExport | Measure-Object).count -gt 0) { + '##### Cmdlets Included' >> $env:GITHUB_STEP_SUMMARY + ($paramNewModuleManifest.CmdletsToExport | ForEach-Object { + "- ``$($_)``" + }) >> $env:GITHUB_STEP_SUMMARY +} +if ($($paramNewModuleManifest.AliasesToExport | Measure-Object).count -gt 0) { + '##### Aliases Included' >> $env:GITHUB_STEP_SUMMARY + ($paramNewModuleManifest.AliasesToExport | ForEach-Object { + "- ``$($_)``" + }) >> $env:GITHUB_STEP_SUMMARY +}