From fa0132f13d7c39884f194e07f14224bedae952a7 Mon Sep 17 00:00:00 2001 From: Lord Hepipud Date: Fri, 18 Feb 2022 16:29:24 +0100 Subject: [PATCH] Adds feature to improve module isolation --- doc/100-General/10-Changelog.md | 1 + doc/900-Developer-Guide/00-General.md | 16 +++- ...ngaForWindowsComponentPublicFunctions.psm1 | 31 +++++++ .../dev/New-IcingaForWindowsComponent.psm1 | 47 ++++++---- .../Publish-IcingaForWindowsComponent.psm1 | 28 +++++- ...ngaForWindowsComponentPublicFunctions.psm1 | 25 ++++++ .../Update-IcingaForWindowsManifestArray.psm1 | 84 +++++++++++++++++ ...ngaForWindowsComponentCompilationFile.psm1 | 60 +++++++++++++ ...ite-IcingaForWindowsComponentManifest.psm1 | 79 +++------------- lib/core/jea/Get-IcingaJEAConfiguration.psm1 | 4 + .../jea/Read-IcingaPowerShellModuleFile.psm1 | 90 ++++++++++++++++--- templates/Manifest.psd1.template | 12 ++- templates/compilation.psm1.template | 19 ++++ 13 files changed, 392 insertions(+), 104 deletions(-) create mode 100644 lib/core/dev/Get-IcingaForWindowsComponentPublicFunctions.psm1 create mode 100644 lib/core/dev/Test-IcingaForWindowsComponentPublicFunctions.psm1 create mode 100644 lib/core/dev/Update-IcingaForWindowsManifestArray.psm1 create mode 100644 lib/core/dev/Write-IcingaForWindowsComponentCompilationFile.psm1 create mode 100644 templates/compilation.psm1.template diff --git a/doc/100-General/10-Changelog.md b/doc/100-General/10-Changelog.md index 9179630..4d5a1c9 100644 --- a/doc/100-General/10-Changelog.md +++ b/doc/100-General/10-Changelog.md @@ -28,6 +28,7 @@ Released closed milestones can be found on [GitHub](https://github.com/Icinga/ic * [#495](https://github.com/Icinga/icinga-powershell-framework/pull/495) Adds feature to check the sign status for the local Icinga Agent certificate and notifying the user, in case the certificate is not yet signed by the Icinga CA * [#496](https://github.com/Icinga/icinga-powershell-framework/pull/496) Improves REST-Api default timeout for internal plugin execution calls from 30s to 120s * [#498](https://github.com/Icinga/icinga-powershell-framework/pull/498) Adds feature for thread queuing optimisation and frozen thread detection for REST calls +* [#514](https://github.com/Icinga/icinga-powershell-framework/pull/514) Adds support for Icinga for Windows module isolation ## 1.8.0 (2022-02-08) diff --git a/doc/900-Developer-Guide/00-General.md b/doc/900-Developer-Guide/00-General.md index 9abb005..00bb25e 100644 --- a/doc/900-Developer-Guide/00-General.md +++ b/doc/900-Developer-Guide/00-General.md @@ -121,6 +121,18 @@ The following entries are set by default within the `Protected` space: | DebugMode | Enables the debug mode of Icinga for Windows, printing additional details during operations or tasks | | Minimal | Changes certain behavior regarding check execution and internal error handling | +## Private and Public Functions + +Icinga for Windows will by default only export `Functions` and `Cmdlets`, in case they are either located within the `root .psm1` file, inside a folder called `public` or in case `Global:` is added before the function: + +```powershell +function Global:Invoke-MyCommand() +``` + +In addition, all commands with the alias `Invoke-IcingaCheck` are automatically exported for plugins. This ensures that each module is isolated from each other and functions with the same name can be used within different modules, without overwriting existing ones. This ensures a better integrity of the module itself. + +Last but not least, each module is created with a compilation file which is created during the first run of the module and used later on. This ensures en even faster response and reduced load on the system. + ## Using Icinga for Windows Dev Tools Maintaining the entire structure above seems to be complicated at the beginning, especially when considering to update the `NestedModules` section whenever you make changes. To mitigate this, Icinga for Windows provides a bunch of Cmdlets to help with the process @@ -139,7 +151,7 @@ The command ships with a bunch of configurations to modify the created `.psd1` i ### Publish/Update Components -Once you have started to write your own code, you can use the Cmdlet `Publish-IcingaForWindowsComponent` to update the `NestedModules` attribute inside the `.psd1` file automatically, including the documentation in case the module is of type plugin. +Once you have started to write your own code, you can use the Cmdlet `Publish-IcingaForWindowsComponent` to compile and add requires functions for the calls, including the documentation in case the module is of type plugin. In addition, you ca create a `.zip` file for this module which can be integrated directly into the [Repository Manager](../120-Repository-Manager/01-Add-Repositories.md). By default, created `.zip` files will be created in your home folder, the path can how ever be changed while executing the command. @@ -149,6 +161,8 @@ In addition, you ca create a `.zip` file for this module which can be integrated | ReleasePackagePath | String | The path on where the `.zip` file will be created in. Defaults to the current users home folder | | CreateReleasePackage | Switch | This will toggle the `.zip` file creation of the specified package | +Please note that using `Publish-IcingaForWindowsComponent` is mandatory, before you can use the module on target systems. Each Icinga for Windows module is isolated from the general environment and allows to overwrite certain functions locally, inside the module instead of having to worry about them being overwritten on other, critical areas. + ### Testing Your Component In order to validate if your module can be loaded and is working properly, you can use the command `Test-IcingaForWindowsComponent`. In addition to an import check, it will also validate the code styling and give you an overview if and how many issues there are with your code. diff --git a/lib/core/dev/Get-IcingaForWindowsComponentPublicFunctions.psm1 b/lib/core/dev/Get-IcingaForWindowsComponentPublicFunctions.psm1 new file mode 100644 index 0000000..677ace0 --- /dev/null +++ b/lib/core/dev/Get-IcingaForWindowsComponentPublicFunctions.psm1 @@ -0,0 +1,31 @@ +function Get-IcingaForWindowsComponentPublicFunctions() +{ + param ( + $FileObject = $null, + [string]$ModuleName = '' + ); + + [array]$ExportFunctions = @(); + + # First first if we are inside a public space + if ((Test-IcingaForWindowsComponentPublicFunctions -FileObject $FileObject -ModuleName $ModuleName) -eq $FALSE) { + $FileData = (Read-IcingaPowerShellModuleFile -File $FileObject.FullName); + + foreach ($entry in $FileData.FunctionList) { + if ($entry.Contains('Global:')) { + $ExportFunctions += $entry.Replace('Global:', ''); + } + } + + $ExportFunctions += $FileData.ExportFunction; + + return $ExportFunctions; + } + + $FileData = (Read-IcingaPowerShellModuleFile -File $FileObject.FullName); + $ExportFunctions += $FileData.FunctionList; + $ExportFunctions += $FileData.ExportFunction; + + # If we are, add all functions we found + return $ExportFunctions; +} diff --git a/lib/core/dev/New-IcingaForWindowsComponent.psm1 b/lib/core/dev/New-IcingaForWindowsComponent.psm1 index f70f6db..169aa7a 100644 --- a/lib/core/dev/New-IcingaForWindowsComponent.psm1 +++ b/lib/core/dev/New-IcingaForWindowsComponent.psm1 @@ -75,13 +75,19 @@ function New-IcingaForWindowsComponent() 'plugins' { New-Item -ItemType Directory -Path (Join-Path -Path $ModuleDir -ChildPath 'plugins') | Out-Null; New-Item -ItemType Directory -Path (Join-Path -Path $ModuleDir -ChildPath 'provider') | Out-Null; + New-Item -ItemType Directory -Path (Join-Path -Path $ModuleDir -ChildPath 'provider\public') | Out-Null; + New-Item -ItemType Directory -Path (Join-Path -Path $ModuleDir -ChildPath 'provider\private') | Out-Null; New-Item -ItemType Directory -Path (Join-Path -Path $ModuleDir -ChildPath 'lib') | Out-Null; + New-Item -ItemType Directory -Path (Join-Path -Path $ModuleDir -ChildPath 'lib\public') | Out-Null; + New-Item -ItemType Directory -Path (Join-Path -Path $ModuleDir -ChildPath 'lib\private') | Out-Null; break; }; 'apiendpoint' { New-Item -ItemType Directory -Path (Join-Path -Path $ModuleDir -ChildPath 'endpoint') | Out-Null; New-Item -ItemType Directory -Path (Join-Path -Path $ModuleDir -ChildPath 'lib') | Out-Null; + New-Item -ItemType Directory -Path (Join-Path -Path $ModuleDir -ChildPath 'lib\public') | Out-Null; + New-Item -ItemType Directory -Path (Join-Path -Path $ModuleDir -ChildPath 'lib\private') | Out-Null; [string]$RegisterFunction = ([string]::Format('Register-IcingaRESTAPIEndpoint{0}', $TextInfo.ToTitleCase($Name.ToLower()))); [string]$RegisterFunctionFile = (Join-Path -Path $ModuleDir -ChildPath ([string]::Format('endpoint\{0}.psm1', $RegisterFunction))); @@ -124,6 +130,8 @@ function New-IcingaForWindowsComponent() 'daemon' { New-Item -ItemType Directory -Path (Join-Path -Path $ModuleDir -ChildPath 'daemon') | Out-Null; New-Item -ItemType Directory -Path (Join-Path -Path $ModuleDir -ChildPath 'lib') | Out-Null; + New-Item -ItemType Directory -Path (Join-Path -Path $ModuleDir -ChildPath 'lib\public') | Out-Null; + New-Item -ItemType Directory -Path (Join-Path -Path $ModuleDir -ChildPath 'lib\private') | Out-Null; New-Item ` -ItemType File ` -Path (Join-Path -Path (Join-Path -Path $ModuleDir -ChildPath 'daemon') -ChildPath ([string]::Format('Start-IcingaForWindowsDaemon{0}.psm1', $TextInfo.ToTitleCase($Name.ToLower())))) | Out-Null; @@ -182,6 +190,8 @@ function New-IcingaForWindowsComponent() }; 'library' { New-Item -ItemType Directory -Path (Join-Path -Path $ModuleDir -ChildPath 'lib') | Out-Null; + New-Item -ItemType Directory -Path (Join-Path -Path $ModuleDir -ChildPath 'lib\public') | Out-Null; + New-Item -ItemType Directory -Path (Join-Path -Path $ModuleDir -ChildPath 'lib\private') | Out-Null; break; } @@ -193,23 +203,26 @@ function New-IcingaForWindowsComponent() -Force | Out-Null; Write-IcingaForWindowsComponentManifest -Name $Name -ModuleConfig @{ - '$MODULENAME$' = ([string]::Format('Windows {0}', $Name)); - '$GUID$' = (New-Guid); - '$ROOTMODULE$' = ([string]::Format('{0}.psm1', $ModuleName)); - '$AUTHOR$' = $Author; - '$COMPANYNAME$' = $CompanyName; - '$COPYRIGHT$' = $Copyright; - '$MODULEVERSION$' = $ModuleVersion.ToString(); - '$VMODULEVERSION$' = ([string]::Format('v{0}', $ModuleVersion.ToString())); - '$DESCRIPTION$' = $Description; - '$REQUIREDMODULES$' = $RequiredModules; - '$NESTEDMODULES$' = ''; - '$TAGS$' = $Tags; - '$PROJECTURI$' = $ProjectUri; - '$LICENSEURI$' = $LicenseUri; - '$COMPONENTTYPE$' = $ComponentType; - '$DAEMONFUNCTION$' = $DaemonFunction; - '$APIENDPOINT$' = $EndpointName; + '$MODULENAME$' = ([string]::Format('Windows {0}', $Name)); + '$GUID$' = (New-Guid); + '$ROOTMODULE$' = ([string]::Format('{0}.psm1', $ModuleName)); + '$AUTHOR$' = $Author; + '$COMPANYNAME$' = $CompanyName; + '$COPYRIGHT$' = $Copyright; + '$MODULEVERSION$' = $ModuleVersion.ToString(); + '$VMODULEVERSION$' = ([string]::Format('v{0}', $ModuleVersion.ToString())); + '$DESCRIPTION$' = $Description; + '$REQUIREDMODULES$' = $RequiredModules; + '$NESTEDMODULES$' = ''; + '$FUNCTIONSTOEXPORT$' = ''; + '$VARIABLESTOEXPORT$' = ''; + '$ALIASESTOEXPORT$' = ''; + '$TAGS$' = $Tags; + '$PROJECTURI$' = $ProjectUri; + '$LICENSEURI$' = $LicenseUri; + '$COMPONENTTYPE$' = $ComponentType; + '$DAEMONFUNCTION$' = $DaemonFunction; + '$APIENDPOINT$' = $EndpointName; }; Set-Content ` diff --git a/lib/core/dev/Publish-IcingaForWindowsComponent.psm1 b/lib/core/dev/Publish-IcingaForWindowsComponent.psm1 index fe1b2d2..fba3813 100644 --- a/lib/core/dev/Publish-IcingaForWindowsComponent.psm1 +++ b/lib/core/dev/Publish-IcingaForWindowsComponent.psm1 @@ -66,28 +66,48 @@ function Publish-IcingaForWindowsComponent() [ScriptBlock]$ManifestScriptBlock = [ScriptBlock]::Create('return ' + $ManifestScript); $ModuleManifestData = (& $ManifestScriptBlock); - $ModuleList = @(); $ModuleFiles = Get-ChildItem -Path $ModuleDir -Recurse -Filter '*.psm1'; + [array]$FunctionList = @(); + [array]$CmdletList = @(); + [array]$VariableList = @(); + [array]$AliasList = @(); + [string]$CompiledFolder = (Join-Path -Path $ModuleDir -ChildPath 'compiled'); + [string]$CompiledFile = [string]::Format('{0}.ifw_compilation.psm1', $ModuleName.ToLower()); + [string]$CompiledFileInclude = [string]::Format('.\compiled\{0}', $CompiledFile); + [string]$CompiledFilePath = (Join-Path -Path $CompiledFolder -ChildPath $CompiledFile); foreach ($entry in $ModuleFiles) { - if ($entry.Name -eq ([string]::Format('{0}.psm1', $ModuleName))) { + # Ensure the compilation file never includes itself + if ($entry.FullName -eq $CompiledFilePath) { continue; } - $ModuleList += $entry.FullName.Replace($ModuleDir, '.'); + $FunctionList += Get-IcingaForWindowsComponentPublicFunctions -FileObject $entry -ModuleName $ModuleName; + $FileConfig = (Read-IcingaPowerShellModuleFile -File $entry.FullName); + $VariableList += $FileConfig.VariableList; + $AliasList += $FileConfig.AliasList; + $CmdletList += $FileConfig.ExportCmdlet; } + if ((Test-Path -Path $CompiledFolder) -eq $FALSE) { + New-Item -Path $CompiledFolder -ItemType Directory -Force | Out-Null; + } + + Copy-ItemSecure -Path (Join-Path -Path (Get-IcingaFrameworkRootPath) -ChildPath 'templates\compilation.psm1.template') -Destination $CompiledFilePath -Force | Out-Null; + if ($NoOutput) { Disable-IcingaFrameworkConsoleOutput; } - Write-IcingaForWindowsComponentManifest -Name $Name -ModuleList $ModuleList; + Write-IcingaForWindowsComponentManifest -Name $Name -ModuleList @( $CompiledFileInclude ) -FunctionList $FunctionList -VariableList $VariableList -AliasList $AliasList -CmdletList $CmdletList; if ($ModuleManifestData.PrivateData.Type -eq 'plugins') { Publish-IcingaPluginConfiguration -ComponentName $Name; Publish-IcingaPluginDocumentation -ModulePath $ModuleDir; } + Copy-ItemSecure -Path (Join-Path -Path (Get-IcingaFrameworkRootPath) -ChildPath 'templates\compilation.psm1.template') -Destination $CompiledFilePath -Force | Out-Null; + if ($CreateReleasePackage) { if ([string]::IsNullOrEmpty($ReleasePackagePath)) { $ReleasePackagePath = Join-Path -Path $ENV:HOMEDRIVE -ChildPath $ENV:HOMEPATH; diff --git a/lib/core/dev/Test-IcingaForWindowsComponentPublicFunctions.psm1 b/lib/core/dev/Test-IcingaForWindowsComponentPublicFunctions.psm1 new file mode 100644 index 0000000..a9a9447 --- /dev/null +++ b/lib/core/dev/Test-IcingaForWindowsComponentPublicFunctions.psm1 @@ -0,0 +1,25 @@ +function Test-IcingaForWindowsComponentPublicFunctions() +{ + param ( + $FileObject = $null, + [string]$ModuleName = '' + ); + + if ($null -eq $FileObject -Or [string]::IsNullOrEmpty($ModuleName)) { + return $FALSE; + } + + # If we load the main .psm1 file of this module, add all functions inside to the public space + if ($FileObject.Name -eq ([string]::Format('{0}.psm1', $ModuleName))) { + return $TRUE; + } + + [int]$RelativPathStartIndex = $FileObject.FullName.IndexOf($ModuleName) + $ModuleName.Length; + $ModuleFileRelativePath = $FileObject.FullName.SubString($RelativPathStartIndex, $FileObject.FullName.Length - $RelativPathStartIndex); + + if ($ModuleFileRelativePath.Contains('\public\') -Or $ModuleFileRelativePath.Contains('\plugins\') -Or $ModuleFileRelativePath.Contains('\endpoint\') -Or $ModuleFileRelativePath.Contains('\daemon\')) { + return $TRUE; + } + + return $FALSE; +} diff --git a/lib/core/dev/Update-IcingaForWindowsManifestArray.psm1 b/lib/core/dev/Update-IcingaForWindowsManifestArray.psm1 new file mode 100644 index 0000000..7d2d113 --- /dev/null +++ b/lib/core/dev/Update-IcingaForWindowsManifestArray.psm1 @@ -0,0 +1,84 @@ +function Update-IcingaForWindowsManifestArray() +{ + param ( + [string]$ManifestFile = '', + [array]$ArrayVariableValues = @(), + [string]$ArrayVariableName = '' + ); + + if ([string]::IsNullOrEmpty($ArrayVariableName)) { + return; + } + + # Remove duplicate entries + $ArrayVariableValues = $ArrayVariableValues | Select-Object -Unique; + + [array]$ManifestContent = Get-Content -Path $ManifestFile -ErrorAction SilentlyContinue; + + if ($null -eq $ManifestContent -Or $ManifestContent.Count -eq 0) { + Write-IcingaConsoleWarning 'The manifest file "{0}" could not be loaded for updating array element "{1}2' -Objects $ManifestFile, $ArrayVariableName; + return; + } + + $ContentString = New-Object -TypeName 'System.Text.StringBuilder'; + [bool]$UpdatedArrayContent = $FALSE; + + foreach ($entry in $ManifestContent) { + [string]$ManifestLine = $entry; + + if ($UpdatedArrayContent -And $entry -Like '*)*') { + $UpdatedArrayContent = $FALSE; + continue; + } + + if ($UpdatedArrayContent) { + continue; + } + + if ($entry -Like ([string]::Format('*{0}*', $ArrayVariableName.ToLower()))) { + if ($entry -NotLike '*)*') { + $UpdatedArrayContent = $TRUE; + } + $ContentString.AppendLine(([string]::Format(' {0} = @(', $ArrayVariableName))) | Out-Null; + + if ($ArrayVariableValues.Count -ne 0) { + [array]$NestedModules = (ConvertFrom-IcingaArrayToString -Array $ArrayVariableValues -AddQuotes -UseSingleQuotes).Split(','); + [int]$ModuleIndex = 0; + foreach ($module in $NestedModules) { + if ([string]::IsNullOrEmpty($module)) { + continue; + } + + $ModuleIndex += 1; + + if ($ModuleIndex -ne $NestedModules.Count) { + if ($ModuleIndex -eq 1) { + $ManifestLine = [string]::Format(' {0},', $module); + } else { + $ManifestLine = [string]::Format(' {0},', $module); + } + } else { + if ($ModuleIndex -eq 1) { + $ManifestLine = [string]::Format(' {0}', $module); + } else { + $ManifestLine = [string]::Format(' {0}', $module); + } + } + + $ContentString.AppendLine($ManifestLine) | Out-Null; + } + } + + $ContentString.AppendLine(' )') | Out-Null; + continue; + } + + if ([string]::IsNullOrEmpty($ManifestLine.Replace(' ', '')) -Or $ManifestLine -eq "`r`n" -Or $ManifestLine -eq "`n") { + continue; + } + + $ContentString.AppendLine($ManifestLine) | Out-Null; + } + + Write-IcingaFileSecure -File $ManifestFile -Value $ContentString.ToString(); +} diff --git a/lib/core/dev/Write-IcingaForWindowsComponentCompilationFile.psm1 b/lib/core/dev/Write-IcingaForWindowsComponentCompilationFile.psm1 new file mode 100644 index 0000000..2e3f2af --- /dev/null +++ b/lib/core/dev/Write-IcingaForWindowsComponentCompilationFile.psm1 @@ -0,0 +1,60 @@ +function Write-IcingaForWindowsComponentCompilationFile() +{ + param ( + [string]$ScriptRootPath = '', + [string]$CompiledFilePath = '' + ); + + # Get the current location and leave this folder + Set-Location -Path $ScriptRootPath; + Set-Location -Path '..'; + + # Store the location of the current file + + # Now as we are inside the module root, get the name of the module and the path + [string]$ModulePath = Get-Location; + [string]$ModuleName = $ModulePath.Split('\')[-1]; + + # Fetch all '.psm1' files from this module content + [array]$ModuleFiles = Get-ChildItem -Path $ModulePath -Recurse -Filter '*.psm1'; + # Get all public functions + [array]$FunctionList = @(); + [array]$VariableList = @(); + [array]$AliasList = @(); + [array]$CmdletList = @(); + # Variable to store all of our module files + [string]$CompiledModule = ''; + + foreach ($entry in $ModuleFiles) { + # Ensure the compilation file never includes itself + if ($entry.FullName -eq $CompiledFilePath) { + continue; + } + + $FunctionList += Get-IcingaForWindowsComponentPublicFunctions -FileObject $entry -ModuleName $ModuleName; + $FileConfig = (Read-IcingaPowerShellModuleFile -File $entry.FullName); + $VariableList += $FileConfig.VariableList; + $AliasList += $FileConfig.AliasList; + $CmdletList += $FileConfig.ExportCmdlet; + $CompiledModule += (Get-Content -Path $entry.FullName -Raw -Encoding 'UTF8'); + $CompiledModule += "`r`n"; + } + + if ((Test-Path -Path $CompiledFilePath) -eq $FALSE) { + New-Item -Path $CompiledFilePath -ItemType File -Force | Out-Null; + } + + $CompiledModule += "`r`n"; + $CompiledModule += [string]::Format( + "Export-ModuleMember -Cmdlet @( {0} ) -Function @( {1} ) -Variable @( {2} ) -Alias @( {3} );", + ((ConvertFrom-IcingaArrayToString -Array ($CmdletList | Select-Object -Unique) -AddQuotes -UseSingleQuotes)), + ((ConvertFrom-IcingaArrayToString -Array ($FunctionList | Select-Object -Unique) -AddQuotes -UseSingleQuotes)), + ((ConvertFrom-IcingaArrayToString -Array ($VariableList | Select-Object -Unique) -AddQuotes -UseSingleQuotes)), + ((ConvertFrom-IcingaArrayToString -Array ($AliasList | Select-Object -Unique) -AddQuotes -UseSingleQuotes)) + ); + + Set-Content -Path $CompiledFilePath -Value $CompiledModule -Encoding 'UTF8'; + + Import-Module -Name $ModulePath -Force; + Import-Module -Name $ModulePath -Force -Global; +} diff --git a/lib/core/dev/Write-IcingaForWindowsComponentManifest.psm1 b/lib/core/dev/Write-IcingaForWindowsComponentManifest.psm1 index dad4e8f..9b36801 100644 --- a/lib/core/dev/Write-IcingaForWindowsComponentManifest.psm1 +++ b/lib/core/dev/Write-IcingaForWindowsComponentManifest.psm1 @@ -17,7 +17,11 @@ function Write-IcingaForWindowsComponentManifest() param ( [string]$Name, [hashtable]$ModuleConfig = @{ }, - [array]$ModuleList = @() + [array]$ModuleList = @(), + [array]$FunctionList = @(), + [array]$CmdletList = @(), + [array]$VariableList = @(), + [array]$AliasList = @() ); if ([string]::IsNullOrEmpty($Name)) { @@ -75,71 +79,14 @@ function Write-IcingaForWindowsComponentManifest() $ContentString.Clear() | Out-Null; - [array]$ManifestContent = Get-Content -Path (Join-Path -Path $ModuleDir -ChildPath ([string]::Format('{0}.psd1', $ModuleName))); + [string]$ManifestFile = (Join-Path -Path $ModuleDir -ChildPath ([string]::Format('{0}.psd1', $ModuleName))); - if ($null -eq $ManifestContent -Or $ManifestContent.Count -eq 0) { - Write-IcingaConsoleWarning 'The manifest file of module "{0}" could not be loaded for updating NestedModules' -Objects $ModuleName; - return; - } + Update-IcingaForWindowsManifestArray -ArrayVariableName 'NestedModules' -ArrayVariableValues $ModuleList -ManifestFile $ManifestFile; + Update-IcingaForWindowsManifestArray -ArrayVariableName 'FunctionsToExport' -ArrayVariableValues $FunctionList -ManifestFile $ManifestFile; + Update-IcingaForWindowsManifestArray -ArrayVariableName 'CmdletsToExport' -ArrayVariableValues $CmdletList -ManifestFile $ManifestFile; + Update-IcingaForWindowsManifestArray -ArrayVariableName 'VariablesToExport' -ArrayVariableValues $VariableList -ManifestFile $ManifestFile; + Update-IcingaForWindowsManifestArray -ArrayVariableName 'AliasesToExport' -ArrayVariableValues $AliasList -ManifestFile $ManifestFile; - [bool]$UpdateNestedModules = $FALSE; - - foreach ($entry in $ManifestContent) { - [string]$ManifestLine = $entry; - - if ($UpdateNestedModules -And $entry -Like '*)*') { - $UpdateNestedModules = $FALSE; - continue; - } - - if ($UpdateNestedModules) { - continue; - } - - if ($entry -Like '*nestedmodules*') { - if ($entry -NotLike '*)*') { - $UpdateNestedModules = $TRUE; - } - $ContentString.AppendLine(' NestedModules = @(') | Out-Null; - - if ($ModuleList.Count -ne 0) { - [array]$NestedModules = (ConvertFrom-IcingaArrayToString -Array $ModuleList -AddQuotes -UseSingleQuotes).Split(','); - [int]$ModuleIndex = 0; - foreach ($module in $NestedModules) { - if ([string]::IsNullOrEmpty($module)) { - continue; - } - - $ModuleIndex += 1; - - if ($ModuleIndex -ne $NestedModules.Count) { - if ($ModuleIndex -eq 1) { - $ManifestLine = [string]::Format(' {0},', $module); - } else { - $ManifestLine = [string]::Format(' {0},', $module); - } - } else { - if ($ModuleIndex -eq 1) { - $ManifestLine = [string]::Format(' {0}', $module); - } else { - $ManifestLine = [string]::Format(' {0}', $module); - } - } - - $ContentString.AppendLine($ManifestLine) | Out-Null; - } - } - - $ContentString.AppendLine(' )') | Out-Null; - continue; - } - - if ([string]::IsNullOrEmpty($ManifestLine.Replace(' ', '')) -Or $ManifestLine -eq "`r`n" -Or $ManifestLine -eq "`n") { - continue; - } - - $ContentString.AppendLine($ManifestLine) | Out-Null; - } - - Write-IcingaFileSecure -File (Join-Path -Path $ModuleDir -ChildPath ([string]::Format('{0}.psd1', $ModuleName))) -Value $ContentString.ToString(); + Import-Module -Name $ManifestFile -Force; + Import-Module -Name $ManifestFile -Force -Global; } diff --git a/lib/core/jea/Get-IcingaJEAConfiguration.psm1 b/lib/core/jea/Get-IcingaJEAConfiguration.psm1 index 2b618c9..4917f97 100644 --- a/lib/core/jea/Get-IcingaJEAConfiguration.psm1 +++ b/lib/core/jea/Get-IcingaJEAConfiguration.psm1 @@ -43,6 +43,10 @@ function Get-IcingaJEAConfiguration() $ModuleFileContent = ''; foreach ($PSFile in $ModuleFiles) { + if ($PSFile.Name.ToLower() -eq ([string]::Format('{0}.ifw_compilation.psm1', $module.Name))) { + continue; + } + $DeserializedFile = Read-IcingaPowerShellModuleFile -File $PSFile.FullName; $RawModuleContent = $DeserializedFile.NormalisedContent; diff --git a/lib/core/jea/Read-IcingaPowerShellModuleFile.psm1 b/lib/core/jea/Read-IcingaPowerShellModuleFile.psm1 index cd65ee3..71c1d23 100644 --- a/lib/core/jea/Read-IcingaPowerShellModuleFile.psm1 +++ b/lib/core/jea/Read-IcingaPowerShellModuleFile.psm1 @@ -13,19 +13,29 @@ function Read-IcingaPowerShellModuleFile() $FileContent = Read-IcingaFileSecure -File $File; } - $PSParser = [System.Management.Automation.PSParser]::Tokenize($FileContent, [ref]$null); - [array]$Comments = @(); - [array]$RegexFilter = @(); - [string]$RegexPattern = ''; - [array]$CommandList = @(); - [array]$FunctionList = @(); - [hashtable]$CmdCache = @{ }; - [hashtable]$FncCache = @{ }; - [int]$Index = 0; - [bool]$ThreadCommand = $FALSE; - [bool]$ThreadFetchNext = $FALSE; - [bool]$ShellCommand = $FALSE; - [bool]$ShellGroupStart = $FALSE; + $PSParser = [System.Management.Automation.PSParser]::Tokenize($FileContent, [ref]$null); + [array]$Comments = @(); + [array]$RegexFilter = @(); + [string]$RegexPattern = ''; + [array]$CommandList = @(); + [array]$FunctionList = @(); + [array]$VariableList = @(); + [array]$AliasList = @(); + [array]$ExportFunctionList = @(); + [array]$ExportCmdletList = @(); + [hashtable]$CmdCache = @{ }; + [hashtable]$FncCache = @{ }; + [int]$Index = 0; + [bool]$ThreadCommand = $FALSE; + [bool]$ThreadFetchNext = $FALSE; + [bool]$ShellCommand = $FALSE; + [bool]$ShellGroupStart = $FALSE; + [bool]$BeginExportMember = $FALSE; + [bool]$BeginExportFunc = $FALSE; + [bool]$BeginExportCmdlet = $FALSE; + [bool]$BeginExportVar = $FALSE; + [bool]$BeginExportAlias = $FALSE; + [bool]$BeginReadExport = $FALSE; foreach ($entry in $PSParser) { if ($entry.Type -eq 'Comment') { @@ -36,6 +46,10 @@ function Read-IcingaPowerShellModuleFile() $CmdCache.Add($entry.Content, 0); } + if ($entry.Content.ToLower() -eq 'export-modulemember') { + $BeginExportMember = $TRUE; + } + # We need to include commands we call with New-IcingaThreadInstance e.g. # => New-IcingaThreadInstance -Name "Main" -ThreadPool (Get-IcingaThreadPool -Name 'MainPool') -Command 'Add-IcingaForWindowsDaemon' -Start; if ($entry.Content.ToLower() -eq 'new-icingathreadinstance') { @@ -54,6 +68,52 @@ function Read-IcingaPowerShellModuleFile() $ShellCommand = $TRUE; } + if ($BeginExportMember) { + if ($entry.Type -eq 'NewLine' -Or ($entry.Type -eq 'StatementSeparator' -And $entry.Content -eq ';')) { + $BeginExportMember = $FALSE; + } + + if ($BeginExportVar -Or $BeginExportAlias -Or $BeginExportFunc -Or $BeginExportCmdlet) { + if ($BeginReadExport) { + if ($entry.Type -ne 'String' -And ($entry.Type -ne 'Operator' -And $entry.Content -ne ',')) { + $BeginReadExport = $FALSE; + $BeginExportVar = $FALSE; + $BeginExportAlias = $FALSE; + $BeginExportFunc = $FALSE; + $BeginExportCmdlet = $FALSE; + } + } + if ($entry.Type -eq 'String') { + $BeginReadExport = $TRUE; + if ($BeginExportVar) { + $VariableList += $entry.Content; + } + if ($BeginExportAlias) { + $AliasList += $entry.Content; + } + if ($BeginExportFunc) { + $ExportFunctionList += $entry.Content; + } + if ($BeginExportCmdlet) { + $ExportCmdletList += $entry.Content; + } + } + } + # Read -Variable argument + if ($entry.Type -eq 'CommandParameter' -And $entry.Content.ToLower() -eq '-variable') { + $BeginExportVar = $TRUE; + } + if ($entry.Type -eq 'CommandParameter' -And $entry.Content.ToLower() -eq '-alias') { + $BeginExportAlias = $TRUE; + } + if ($entry.Type -eq 'CommandParameter' -And $entry.Content.ToLower() -eq '-function') { + $BeginExportFunc = $TRUE; + } + if ($entry.Type -eq 'CommandParameter' -And $entry.Content.ToLower() -eq '-cmdlet') { + $BeginExportCmdlet = $TRUE; + } + } + # If we reached -Command for New-IcingaThreadInstance, check for the String element and add its value to our function list e.g. # => Add-IcingaForWindowsDaemon if ($ThreadFetchNext) { @@ -113,5 +173,9 @@ function Read-IcingaPowerShellModuleFile() 'RawContent' = $FileContent; 'CommandList' = $CommandList; 'FunctionList' = $FunctionList; + 'VariableList' = $VariableList; + 'AliasList' = $AliasList; + 'ExportFunction' = $ExportFunctionList; + 'ExportCmdlet' = $ExportCmdletList; }; } diff --git a/templates/Manifest.psd1.template b/templates/Manifest.psd1.template index c8475a8..1357305 100644 --- a/templates/Manifest.psd1.template +++ b/templates/Manifest.psd1.template @@ -13,10 +13,16 @@ NestedModules = @( $NESTEDMODULES$ ) - FunctionsToExport = @( '*' ) + FunctionsToExport = @( + $FUNCTIONSTOEXPORT$ + ) CmdletsToExport = @( '*' ) - VariablesToExport = @( '*' ) - AliasesToExport = @( '*' ) + VariablesToExport = @( + $VARIABLESTOEXPORT$ + ) + AliasesToExport = @( + $ALIASESTOEXPORT$ + ) PrivateData = @{ PSData = @{ Tags = @( $TAGS$ ) diff --git a/templates/compilation.psm1.template b/templates/compilation.psm1.template new file mode 100644 index 0000000..531c7d1 --- /dev/null +++ b/templates/compilation.psm1.template @@ -0,0 +1,19 @@ +<# + Icinga for Windows Component compilation file. + Will be overwritten to defaults with every update and contains + all code pre-compiled for faster execution and for providing + private/public commands. + + Fetches the current module and file location, reads all .psm1 files and compiles them into one large environment +#> +if ($null -eq (Get-Command -Name 'Write-IcingaForWindowsComponentCompilationFile' -ErrorAction SilentlyContinue)) { + Write-Host '[' -NoNewline; + Write-Host 'Error' -ForegroundColor Red -NoNewline; + Write-Host ([string]::Format(']: Failed to compile Icinga for Windows component at location "{0}", because the required function "Write-IcingaForWindowsComponentCompilationFile" is not installed. Please ensure Icinga PowerShell Framework v1.9.0 or later is installed and try again.', $MyInvocation.MyCommand.Path)); + + return; +} + +Write-IcingaForWindowsComponentCompilationFile ` + -ScriptRootPath $PSScriptRoot ` + -CompiledFilePath ($MyInvocation.MyCommand.Path);