diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 00000000..2287a484
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,34 @@
+on:
+ push:
+ branches:
+ - master
+ - main
+
+jobs:
+ build:
+
+ runs-on: windows-latest
+ permissions:
+ contents: write
+
+ steps:
+ - uses: actions/checkout@v1
+ - name: Install Prerequisites
+ run: .\build\psf-prerequisites.ps1
+ shell: powershell
+ - name: Install Prerequisites
+ run: .\build\vsts-help.ps1
+ shell: powershell
+ - name: Validate
+ run: .\build\vsts-validate.ps1
+ shell: powershell
+ - name: Build
+ run: .\build\vsts-build.ps1 -ApiKey $env:APIKEY
+ shell: powershell
+ env:
+ APIKEY: ${{ secrets.ApiKey }}
+ - name: Release
+ run: .\build\vsts-release.ps1
+ shell: powershell
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
new file mode 100644
index 00000000..3a51c80f
--- /dev/null
+++ b/.github/workflows/validate.yml
@@ -0,0 +1,18 @@
+on: [pull_request]
+
+jobs:
+ validate:
+
+ runs-on: windows-latest
+
+ steps:
+ - uses: actions/checkout@v1
+ - name: Install Prerequisites
+ run: .\build\psf-prerequisites.ps1
+ shell: powershell
+ - name: Install Prerequisites
+ run: .\build\vsts-help.ps1
+ shell: powershell
+ - name: Validate
+ run: .\build\vsts-validate.ps1
+ shell: powershell
\ No newline at end of file
diff --git a/PSFramework/PSFramework.psd1 b/PSFramework/PSFramework.psd1
index ccf09e7d..4c82df50 100644
--- a/PSFramework/PSFramework.psd1
+++ b/PSFramework/PSFramework.psd1
@@ -4,7 +4,7 @@
RootModule = 'PSFramework.psm1'
# Version number of this module.
- ModuleVersion = '1.12.346'
+ ModuleVersion = '1.13.406'
# ID used to uniquely identify this module
GUID = '8028b914-132b-431f-baa9-94a6952f21ff'
@@ -38,12 +38,14 @@
'Add-PSFFilterCondition'
'Add-PSFLoggingProviderRunspace'
'Add-PSFRunspaceWorker'
+ 'Add-PSFTeppCompletion'
'Clear-PSFMessage'
'Clear-PSFresultCache'
'Compare-PSFArray'
'ConvertFrom-PSFArray'
'ConvertFrom-PSFClixml'
'ConvertTo-PSFClixml'
+ 'ConvertTo-PSFPsd1'
'Disable-PSFConsoleInterrupt'
'Disable-PSFLoggingProvider'
'Disable-PSFTaskEngineTask'
@@ -51,12 +53,15 @@
'Enable-PSFTaskEngineTask'
'Export-PSFClixml'
'Export-PSFConfig'
+ 'Export-PSFJson'
'Export-PSFModuleClass'
+ 'Export-PSFPowerShellDataFile'
'Get-PSFCallback'
'Get-PSFConfig'
'Get-PSFConfigValue'
'Get-PSFDynamicContentObject'
'Get-PSFFeature'
+ 'Get-PSFFileContent'
'Get-PSFFilterCondition'
'Get-PSFFilterConditionSet'
'Get-PSFLicense'
@@ -77,11 +82,13 @@
'Get-PSFTaskEngineCache'
'Get-PSFTaskEngineTask'
'Get-PSFTempItem'
+ 'Get-PSFTeppCompletion'
'Get-PSFTypeSerializationData'
'Get-PSFUserChoice'
'Import-PSFClixml'
'Import-PSFCmdlet'
'Import-PSFConfig'
+ 'Import-PSFJson'
'Import-PSFLocalizedString'
'Import-PSFLoggingProvider'
'Import-PSFPowerShellDataFile'
@@ -115,6 +122,7 @@
'Register-PSFMessageEvent'
'Register-PSFMessageTransform'
'Register-PSFParameterClassMapping'
+ 'Register-PSFPsd1Converter'
'Register-PSFRunspace'
'Register-PSFSessionObjectType'
'Register-PSFSupportDataProvider'
@@ -131,6 +139,7 @@
'Remove-PSFMessageLevelModifier'
'Remove-PSFRunspaceWorkflow'
'Remove-PSFTempItem'
+ 'Remove-PSFTeppCompletion'
'Reset-PSFConfig'
'Resolve-PSFDefaultParameterValue'
'Resolve-PSFItem'
@@ -139,6 +148,7 @@
'Select-PSFPropertyValue'
'Set-PSFDynamicContentObject'
'Set-PSFFeature'
+ 'Set-PSFFileContent'
'Set-PSFLoggingProvider'
'Set-PSFPath'
'Set-PSFResultCache'
@@ -173,6 +183,7 @@
# Cmdlets to export from this module
CmdletsToExport = @(
+ 'Assert-PSFInternalCommand'
'ConvertTo-PSFHashtable'
'Invoke-PSFCallback'
'Invoke-PSFProtectedCommand'
@@ -181,6 +192,7 @@
'Set-PSFConfig'
'Set-PSFObjectOrder'
'Test-PSFShouldProcess'
+ 'Update-PSFTeppCompletion'
'Write-PSFMessage'
)
diff --git a/PSFramework/bin/PSFramework.dll b/PSFramework/bin/PSFramework.dll
index 0ec05368..b7090b20 100644
Binary files a/PSFramework/bin/PSFramework.dll and b/PSFramework/bin/PSFramework.dll differ
diff --git a/PSFramework/bin/PSFramework.pdb b/PSFramework/bin/PSFramework.pdb
index a7b805e5..274101c9 100644
Binary files a/PSFramework/bin/PSFramework.pdb and b/PSFramework/bin/PSFramework.pdb differ
diff --git a/PSFramework/bin/PSFramework.xml b/PSFramework/bin/PSFramework.xml
index 82f48642..f5048d37 100644
--- a/PSFramework/bin/PSFramework.xml
+++ b/PSFramework/bin/PSFramework.xml
@@ -4,6 +4,21 @@
PSFramework
+
+
+ Amazingly useful command, that verifies that the command being executed was called from another command in the same module.
+
+
+
+
+ The $PSCmdlet variable of the calling function. This allows us to throw the error from the calling command and hide our cmdlet to the end user.
+
+
+
+
+ The main implementation, doing the various checks needed to ensure it is only called internally.
+
+
Command executing callbacks in functions
@@ -143,6 +158,17 @@
Execute end step
+
+
+ Imports provided values to parameters configured for an auto-training completer.
+
+
+
+
+ Execute the main (and only) step: Copying over values provided to the calling command as parameter into the completion cache.
+ Only applies to parameters configured for auto-training tab completers.
+
+
Implements the ConvertTo-PSFHashtable command
@@ -174,6 +200,11 @@
Enabling this switch has the command inherit values from variables in the caller scope if their name has been specified in the Include parameter.
+
+
+ Inherit from all parameters of the calling command, including all non-bound parameters.
+
+
Remap individual keys in the hashtable provided.
@@ -303,6 +334,11 @@
The message level at which to generate non-error messages written by this cmdlet
+
+
+ Make the final error generated a nonterminating error
+
+
Information on the calling command, including name, module, file and line.
@@ -390,7 +426,7 @@
The name of the feature to check.
Whether the flag is enabled
-
+
Write a message using the PSFramework. Executed as scriptblock, so the current runspace must not be occupied elseways
@@ -403,8 +439,9 @@
Tags to attach to this message
A target object to specify
Add additional metadata to the message written
+ Exception to include in the message
-
+
Write a message using the PSFramework. Executed as scriptblock, so the current runspace must not be occupied elseways
@@ -418,6 +455,7 @@
Tags to attach to this message
A target object to specify
Add additional metadata to the message written
+ Exception to include in the message
@@ -453,6 +491,12 @@
Whether the command has been set to terminate by StopCommand. Use when supporting EnableException
+
+
+ Throw a continue exception, equivalent to calling continue in script
+
+
+
Invokes all applicable callback scripts.
@@ -620,6 +664,13 @@
Implements the end action of the command
+
+
+ Converts a select-object result based on extra elements to add
+
+ The item to convert / extend
+ A fully converted object
+
Implements the Set-PSFConfig command
@@ -952,6 +1003,12 @@
This is less user friendly, but allows catching exceptions in calling scripts.
+
+
+ Create a new error record, using the current message, then write it.
+ This parameter is only respected if EnableException is set to true.
+
+
Enables breakpoints on the current message. By default, setting '-Debug' will NOT cause an interrupt on the current position.
@@ -1521,6 +1578,457 @@
The value is something indeterminate, but possibly complex
+
+
+ Converts data structures into PSD1 Strings
+
+
+
+
+ Converts to psd1 string
+
+ The object to convert
+ The indentation level
+ The conversion runtime object, including its settings.
+ The parent objects. Used in complex data types to prevent infinite recursion.
+ The converted PSD1 representation of the value provided
+
+
+
+ Converts data structures into PSD1 Strings
+
+
+
+
+ Converts to psd1 string
+
+ The object to convert
+ The indentation level
+ The conversion runtime object, including its settings.
+ The parent objects. Used in complex data types to prevent infinite recursion.
+ The converted PSD1 representation of the value provided
+
+
+
+ Converts data structures into PSD1 Strings
+
+
+
+
+ Converts to psd1 string
+
+ The object to convert
+ The indentation level
+ The conversion runtime object, including its settings.
+ The parent objects. Used in complex data types to prevent infinite recursion.
+ The converted PSD1 representation of the value provided
+
+
+
+ Converts data structures into PSD1 Strings
+
+
+
+
+ The type the custom converter is assigned to
+
+
+
+
+ The code calculating the new PSD1-String
+
+
+
+
+ Any additional properties to assign to this.
+ These properties are made available to the code via the $this variable
+
+
+
+
+ The result to return as a default.
+ This is mostly used in combination with "OnErrorDefault"
+
+
+
+
+ Whether in case of an error it should just return a default string, rather than failing the entire conversion.
+
+
+
+
+ Converts to psd1 string
+
+ The object to convert
+ The indentation level
+ The conversion runtime object, including its settings.
+ The parent objects. Used in complex data types to prevent infinite recursion.
+ The converted PSD1 representation of the value provided
+
+
+
+ Creates a new custom converter
+
+ The type that should be converted
+ The code doing the conversion
+
+
+
+ Converts data structures into PSD1 Strings
+
+
+
+
+ Converts to psd1 string
+
+ The object to convert
+ The indentation level
+ The conversion runtime object, including its settings.
+ The parent objects. Used in complex data types to prevent infinite recursion.
+ The converted PSD1 representation of the value provided
+
+
+
+ Converts data structures into PSD1 Strings
+
+
+
+
+ Converts to psd1 string
+
+ The object to convert
+ The indentation level
+ The conversion runtime object, including its settings.
+ The parent objects. Used in complex data types to prevent infinite recursion.
+ The converted PSD1 representation of the value provided
+
+
+
+ Converts data structures into PSD1 Strings
+
+
+
+
+ Converts to psd1 string
+
+ The object to convert
+ The indentation level
+ The conversion runtime object, including its settings.
+ The parent objects. Used in complex data types to prevent infinite recursion.
+ The converted PSD1 representation of the value provided
+
+
+
+ Converts data structures into PSD1 Strings
+
+
+
+
+ Converts to psd1 string
+
+ The object to convert
+ The indentation level
+ The conversion runtime object, including its settings.
+ The parent objects. Used in complex data types to prevent infinite recursion.
+ The converted PSD1 representation of the value provided
+
+
+
+ Converts data structures into PSD1 Strings
+
+
+
+
+ Converts to psd1 string
+
+ The object to convert
+ The indentation level
+ The conversion runtime object, including its settings.
+ The parent objects. Used in complex data types to prevent infinite recursion.
+ The converted PSD1 representation of the value provided
+
+
+
+ Converts data structures into PSD1 Strings
+
+
+
+
+ Converts to psd1 string
+
+ The object to convert
+ The indentation level
+ The conversion runtime object, including its settings.
+ The parent objects. Used in complex data types to prevent infinite recursion.
+ The converted PSD1 representation of the value provided
+
+
+
+ Converts data structures into PSD1 Strings
+
+
+
+
+ Converts to psd1 string
+
+ The object to convert
+ The indentation level
+ The conversion runtime object, including its settings.
+ The parent objects. Used in complex data types to prevent infinite recursion.
+ The converted PSD1 representation of the value provided
+
+
+
+ Converts data structures into PSD1 Strings
+
+
+
+
+ Converts to psd1 string
+
+ The object to convert
+ The indentation level
+ The conversion runtime object, including its settings.
+ The parent objects. Used in complex data types to prevent infinite recursion.
+ The converted PSD1 representation of the value provided
+
+
+
+ Converts data structures into PSD1 Strings
+
+
+
+
+ Converts to psd1 string
+
+ The object to convert
+ The indentation level
+ The conversion runtime object, including its settings.
+ The parent objects. Used in complex data types to prevent infinite recursion.
+ The converted PSD1 representation of the value provided
+
+
+
+ Converts data structures into PSD1 Strings
+
+
+
+
+ Converts to psd1 string
+
+ The object to convert
+ The indentation level
+ The conversion runtime object, including its settings.
+ The parent objects. Used in complex data types to prevent infinite recursion.
+ The converted PSD1 representation of the value provided
+
+
+
+ Converts data structures into PSD1 Strings
+
+
+
+
+ Converts to psd1 string
+
+ The object to convert
+ The indentation level
+ The conversion runtime object, including its settings.
+ The parent objects. Used in complex data types to prevent infinite recursion.
+ The converted PSD1 representation of the value provided
+
+
+
+ Converts data structures into PSD1 Strings
+
+
+
+
+ Converts to psd1 string
+
+ The object to convert
+ The indentation level
+ The conversion runtime object, including its settings.
+ The parent objects. Used in complex data types to prevent infinite recursion.
+ The converted PSD1 representation of the value provided
+
+
+
+ Converts data structures into PSD1 Strings
+
+
+
+
+ Converts to psd1 string
+
+ The object to convert
+ The indentation level
+ The conversion runtime object, including its settings.
+ The parent objects. Used in complex data types to prevent infinite recursion.
+ The converted PSD1 representation of the value provided
+
+
+
+ Converts data structures into PSD1 Strings
+
+
+
+
+ Converts to psd1 string
+
+ The object to convert
+ The indentation level
+ The conversion runtime object, including its settings.
+ The parent objects. Used in complex data types to prevent infinite recursion.
+ The converted PSD1 representation of the value provided
+
+
+
+ Converts data structures into PSD1 Strings
+
+
+
+
+ Converts to psd1 string
+
+ The object to convert
+ The indentation level
+ The conversion runtime object, including its settings.
+ The parent objects. Used in complex data types to prevent infinite recursion.
+ The converted PSD1 representation of the value provided
+
+
+
+ Converts data structures into PSD1 Strings
+
+
+
+
+ Converts to psd1 string
+
+ The object to convert
+ The indentation level
+ The conversion runtime object, including its settings.
+ The parent objects. Used in complex data types to prevent infinite recursion.
+ The converted PSD1 representation of the value provided
+
+
+
+ Global Helper Tools for dealing with Data
+
+
+
+
+ The registered list of PSD1 converters
+
+
+
+
+ The fallback converter to use when all else fails
+
+
+
+
+ The converter to use for dictionaries, such as Hashtables
+
+
+
+
+ Convert an object to the string to insert into a psd1 document.
+
+ The value to convert
+ The parent objects. Used in complex data types to prevent infinite recursion.
+ The current indentation depth. Can be ignored for plain value returns, if you do not need to return multiple lines.
+ The conversion runtime object, including its settings.
+ The converted PSD1 representation of the value provided
+
+
+
+ Resets the PSD1 Converters to their default state
+
+
+
+
+ Ruleset for types implementing PSD1 Conversion
+
+
+
+
+ Convert an object to the string to insert into a psd1 document.
+
+ The value to convert
+ The parent objects. Used in complex data types to prevent infinite recursion.
+ The current indentation depth. Can be ignored for plain value returns, if you do not need to return multiple lines.
+ The conversion runtime object, including its settings.
+ The converted PSD1 representation of the value provided
+
+
+
+ The PSD1 conversion runtime object.
+ Use this to transport any settings conversion plugins require.
+
+
+
+
+ The reference to the PowerShell command running this conversion.
+ Can be used to directly interact with the current runtime, send messages or throw errors.
+ May be null.
+
+
+
+
+ How deeply nested are we willing to delve into sub-properties?
+ By default, this is only respected for PSObjects or Dictionaries.
+
+
+
+
+ Extra configuration settings.
+ This will be ignored by the default, builtin converters, but external ones may use this to transport and implement settings.
+
+
+
+
+ Enable verbose messages.
+
+
+
+
+ Convert some object to PSD1
+
+ The object to convert to PSD1
+ A PSD1-string
+
+
+
+ Convert some object to PSD1
+
+ The object to convert to PSD1
+ How deeply indented do we start?
+ What parent objects came before us? Helps prevent infinite recursion.
+ A PSD1-string
+
+
+
+ Send a message (maybe)
+
+
+
+
+
+ Escapes a string's single quotes of all flavors.
+
+ The text to escape
+ A properly escaped string
+
+
+
+ Escapes a string's single quotes of all flavors.
+ Ensures content is converted to text using PowerShell rules first.
+
+ The text to escape
+ A properly escaped string
+
An individual filter condition.
@@ -3269,6 +3777,45 @@
Generation 2 logging provider, where each provider is handled as a dynamically created module, properly isolating resource from each other.
+
+
+ Helper class to properly format logging messages for the TXT-Variant of the logfile provider
+
+
+
+
+ The text pattern provided by the user, describing the way the logfile is supposed to look.
+
+
+
+
+ The properties that exist within the text pattern. Used to optimize the content replacement process.
+
+
+
+
+ Create an empty text-builder
+
+
+
+
+ Create a text-builder preconfigured with a text to build
+
+ The text to build. Use "%PropertyName%" to offer placeholders that later values get inserted into.
+
+
+
+ Load a new text to build.
+
+ The text to build. Use "%PropertyName%" to offer placeholders that later values get inserted into.
+
+
+
+ Take a message object and build it into a string, ready for logging
+
+ The message object to build.
+ The finished message, ready for the logfile.
+
Container for a callstack, to create a non-volatile copy of the relevant information
@@ -4013,6 +4560,11 @@
Include message timestamps in verbose message output
+
+
+ Include the message target (if present) in verbose message output
+
+
Include the level of a message in verbose message output
@@ -4038,6 +4590,11 @@
Whether the console messages should be written with only one color, rather than respecting color tags
+
+
+ The format used in messages written on screen
+
+
Define the message prefix value for the critical level
@@ -4860,18 +5417,179 @@
The category of the error, determining the exception type
An exception
-
+
- Provides centralized utilities for interacting with PowerShell objects.
+ Wrapper exposing a PowerShell scope and all its content.
-
+
- Add a set of noteproperties to the specified PowerShell object
+ What kind of scope it is.
- The object to extend
- The set of properties to add
- Whether validation has already been performed and need not be done. Setting this to false will add performance overhead.
+
+
+
+ Creates an empty scope object
+
+
+
+
+ Creates a scope object, wrapping around a previously reflected PS scope object.
+
+ The raw scope object, as obtained from the PowerShell engine via reflection.
+
+
+
+ Get a representation of the global scope.
+
+ a representation of the global scope
+
+
+
+ Get a representation of the module scope.
+
+ a representation of the module scope
+
+
+
+ Get a representation of the script scope.
+
+ a representation of the script scope
+
+
+
+ Get a representation of the current scope.
+
+ a representation of the current scope
+
+
+
+ Enable access to variable content of the scope.
+
+
+
+
+ Disable access to variable content of the scope.
+
+
+
+
+ The variables contained in the scope
+
+
+
+
+ Checks whether a specific variable exists in the scope
+
+ Name of the variable to scan for
+ Whether the variable exists
+
+
+
+ Retrieve the value of a variable in the scope.
+ Returns null if scope or variable do not exist.
+
+ Name of the variable to retrieve.
+ The value of the variable.
+
+
+
+ Change the value of a variable, in effect overwriting it.
+
+ The name of the variable to apply
+ The value to assign to the variable
+
+
+
+ Enable access to alias content of the scope.
+
+
+
+
+ Disable access to alias content of the scope.
+
+
+
+
+ The aliases of the scope
+
+
+
+
+ Enable access to function content of the scope.
+
+
+
+
+ Disable access to function content of the scope.
+
+
+
+
+ The functions defined within the scope.
+
+
+
+
+ Enable access to cmdlet content of the scope.
+
+
+
+
+ Disable access to cmdlet content of the scope.
+
+
+
+
+ The Cmdlets contained within the scope
+
+
+
+
+ Enable access to PSDrivecontent of the scope.
+
+
+
+
+ Disable access to PSDrivecontent of the scope.
+
+
+
+
+ The PSDrives contained within the scope
+
+
+
+
+ The Parent Scope of the current scope
+
+
+
+
+ Update the assigned Type.
+ Intended when generating a scope based on another scope.
+
+
+
+
+ Provides centralized utilities for interacting with PowerShell objects.
+
+
+
+
+ Add a set of noteproperties to the specified PowerShell object
+
+ The object to extend
+ The set of properties to add
+ Whether validation has already been performed and need not be done. Setting this to false will add performance overhead.
+
+
+
+ Add a set of noteproperties to all specified PowerShell objects
+
+ The objects to extend
+ The set of properties to add
+ Whether validation has already been performed and need not be done. Setting this to false will add performance overhead.
@@ -4882,6 +5600,15 @@
The value to assign to the new property
Whether validation has already been performed and need not be done. Setting this to false will add performance overhead.
+
+
+ Adds a single noteproperty to a lot of specified PowerShell objects
+
+ The objects to extend
+ The name of the property to add
+ The value to assign to the new property
+ Whether validation has already been performed and need not be done. Setting this to false will add performance overhead.
+
Adds a script-calculated property to the specified PowerShell object
@@ -4892,6 +5619,16 @@
(optional) The code used when assigning input to the property.
Whether validation has already been performed and need not be done. Setting this to false will add performance overhead.
+
+
+ Adds a script-calculated property to the specified PowerShell objects
+
+ The objects to extend
+ The name of the script-property to add
+ The code used to read the value
+ (optional) The code used when assigning input to the property.
+ Whether validation has already been performed and need not be done. Setting this to false will add performance overhead.
+
Removes a member from the specified PowerShell object
@@ -4899,6 +5636,13 @@
The object to remove a member from
The name of the member to remove.
+
+
+ Removes a member from the specified PowerShell objects
+
+ The objects to remove a member from
+ The name of the member to remove.
+
Adds a script-based method to the specified PowerShell object
@@ -4908,6 +5652,15 @@
The code implementing the logic when the method is called.
Whether validation has already been performed and need not be done. Setting this to false will add performance overhead.
+
+
+ Adds a script-based method to the specified PowerShell objects
+
+ The objects to extend
+ The name of the method to add.
+ The code implementing the logic when the method is called.
+ Whether validation has already been performed and need not be done. Setting this to false will add performance overhead.
+
Adds several script methods to the specified PowerShell object
@@ -4916,6 +5669,14 @@
The methods to add. Provide a hashtable mapping method name to scriptblock with the logic implementing the method.
Whether validation has already been performed and need not be done. Setting this to false will add performance overhead.
+
+
+ Adds several script methods to the specified PowerShell objects
+
+ The objects to extend
+ The methods to add. Provide a hashtable mapping method name to scriptblock with the logic implementing the method.
+ Whether validation has already been performed and need not be done. Setting this to false will add performance overhead.
+
Extend default hashtables to include a default value
@@ -4926,6 +5687,16 @@
The default value when retrieving values for keys that do not exist
+
+
+ Whether in case of unexpected key, the key should be returned, rather than a default value.
+
+
+
+
+ Scriptblock used to calculate result when providing a key that is not applied to the hashtable.
+
+
Creates a new, empty psfhashtable
@@ -4943,6 +5714,22 @@
The default value when accessing this PsfHashtable's values
+
+
+ Enables the PassThru behavior, where an unexpected key gets returned as the default action
+
+
+
+
+ Disables the PassThru behavior, where an unexpected key would get returned as the default action
+
+
+
+
+ Sets the scriptblock used to calculate the results for keys, that are not registered with the hashtable
+
+ The logic doing the calculating. Provide null to disable.
+
Create a copy of the current PsfHashtable, including its default value. The default value will be the same instance of an object.
@@ -5305,6 +6092,112 @@
+
+
+ Parameterclass that accepts a single directory
+
+
+
+
+ Processes a single string as a single directory.
+
+ The path to process
+
+
+
+ Processes a single DirectoryInfo item as a single directory.
+
+ The path to process
+
+
+
+ Processes a single Uri as a single directory.
+
+ The path to process
+
+
+
+ Processes a single object as a single directory.
+
+ The path to process
+
+
+
+ Implicitly convert to string.
+
+ The path to convert
+
+
+
+ Implicitly convert to DirectoryInfo.
+
+ The path to convert
+
+
+
+ Implicitly convert DirectoryInfo to PathDirectorySingleParameter.
+
+ The path to convert
+
+
+
+ Implicitly convert to FileSystemInfo.
+
+ The path to convert
+
+
+
+ Parameterclass accepting/resolving to a single, fully-resolved path to a file.
+
+
+
+
+ Processes a single string as a single file.
+
+ The path to process
+
+
+
+ Processes a single FileInfo item as a single file.
+
+ The path to process
+
+
+
+ Processes a single Uri as a single file.
+
+ The path to process
+
+
+
+ Processes a single object as a single file.
+
+ The path to process
+
+
+
+ Implicitly convert to string.
+
+ The path to convert
+
+
+
+ Implicitly convert to FileInfo.
+
+ The path to convert
+
+
+
+ Implicitly convert FileInfo to PathFileSingle.
+
+ The path to convert
+
+
+
+ Implicitly convert to FileSystemInfo.
+
+ The path to convert
+
Resolves any file or folder evaluating wildcards, will not throw on bad input
@@ -5782,6 +6675,228 @@
+
+
+ Parameter Class that maps to a single file or folder
+
+
+
+
+ Processes a single string as a single directory or file.
+
+ The path to process
+
+
+
+ Processes a single DirectoryInfo item as a single directory.
+
+ The path to process
+
+
+
+ Processes a single FileInfo item as a single file.
+
+ The path to process
+
+
+
+ Processes a single Uri as a single directory or file.
+
+ The path to process
+
+
+
+ Processes a single object as a single directory or file.
+
+ The path to process
+
+
+
+ Implicitly convert to string.
+
+ The path to convert
+
+
+
+ Implicitly convert to DirectoryInfo.
+
+ The path to convert
+
+
+
+ Implicitly convert to FileInfo.
+
+ The path to convert
+
+
+
+ Implicitly convert DirectoryInfo to PathFileSystemSingleParameter.
+
+ The path to convert
+
+
+
+ Implicitly convert FileInfo to PathFileSystemSingleParameter.
+
+ The path to convert
+
+
+
+ Implicitly convert to FileSystemInfo.
+
+ The path to convert
+
+
+
+ Base class for parameter classes mapping to a single filesystem item
+
+
+
+
+ The path resolved
+
+
+
+
+ Default text representation of the PathFileSingle type.
+
+ Its path.
+
+
+
+ Resolve the path provided and ensure it exists and is of the proper type!
+
+ The path to resolve
+ Whether it may be a file
+ Whether it may be a directory
+ When the path provided cannot be resolved at all.
+ When the path provided resolves to multiple items.
+ When the path should resolve to a file, but is not a file.
+ When the path should resolve to a directory, but is not a directory.
+
+
+
+ Verify the specified path exists, process relative paths, but do not apply wildcards.
+
+ The path to process
+ Whether the path may point to a file
+ Whether the path may point to a directory
+ Thrown if the item does not exist at all
+ When the path should resolve to a file, but is not a file.
+ When the path should resolve to a directory, but is not a directory.
+
+
+
+ Implicitly convert to string.
+
+ The path to convert
+
+
+
+ Parameter class that maps to a single directory.
+
+
+
+
+ Processes a single string as a single directory.
+
+ The path to process
+
+
+
+ Processes a single DirectoryInfo item as a single directory.
+
+ The path to process
+
+
+
+ Processes a single Uri as a single directory.
+
+ The path to process
+
+
+
+ Processes a single object as a single directory.
+
+ The path to process
+
+
+
+ Implicitly convert to string.
+
+ The path to convert
+
+
+
+ Implicitly convert to DirectoryInfo.
+
+ The path to convert
+
+
+
+ Implicitly convert DirectoryInfo to PathLiteralDirectorySingleParameter.
+
+ The path to convert
+
+
+
+ Implicitly convert to FileSystemInfo.
+
+ The path to convert
+
+
+
+ Parameter Class that maps to a single file without processing wildcards
+
+
+
+
+ Processes a single string as a single file.
+
+ The path to process
+
+
+
+ Processes a single FileInfo item as a single file.
+
+ The path to process
+
+
+
+ Processes a single Uri as a single file.
+
+ The path to process
+
+
+
+ Processes a single object as a single file.
+
+ The path to process
+
+
+
+ Implicitly convert to string.
+
+ The path to convert
+
+
+
+ Implicitly convert to FileInfo.
+
+ The path to convert
+
+
+
+ Implicitly convert FileInfo to PathLiteralFileSingleParameter.
+
+ The path to convert
+
+
+
+ Implicitly convert to FileSystemInfo.
+
+ The path to convert
+
Input converter performing path evaluation without wildcard interpretation, not throwing errors on bad input
@@ -5908,6 +7023,77 @@
+
+
+ Parameter Class that maps to a single file or directory, without resolving wildcards.
+
+
+
+
+ Processes a single string as a single directory or file.
+
+ The path to process
+
+
+
+ Processes a single DirectoryInfo item as a single directory.
+
+ The path to process
+
+
+
+ Processes a single FileInfo item as a single file.
+
+ The path to process
+
+
+
+ Processes a single Uri as a single directory or file.
+
+ The path to process
+
+
+
+ Processes a single object as a single directory or file.
+
+ The path to process
+
+
+
+ Implicitly convert to string.
+
+ The path to convert
+
+
+
+ Implicitly convert to DirectoryInfo.
+
+ The path to convert
+
+
+
+ Implicitly convert to DirectoryInfo.
+
+ The path to convert
+
+
+
+ Implicitly convert DirectoryInfo to PathDirectorySingleParameter.
+
+ The path to convert
+
+
+
+ Implicitly convert FileInfo to PathDirectorySingleParameter.
+
+ The path to convert
+
+
+
+ Implicitly convert to FileSystemInfo.
+
+ The path to convert
+
Parameter class, converting input into the path to a file, where either the file or at least its parent folder already exist.
@@ -5957,6 +7143,59 @@
+
+
+ ParameterClass to map to a single file that needs not exist, but whose parent directory must.
+
+
+
+
+ Processes a single string as a single file.
+
+ The path to process
+
+
+
+ Processes a single FileInfo item as a single file.
+
+ The path to process
+
+
+
+ Processes a single Uri as a single file.
+
+ The path to process
+
+
+
+ Processes a single object as a single file.
+
+ The path to process
+
+
+
+ Implicitly convert to string.
+
+ The path to convert
+
+
+
+ Implicitly convert to FileInfo.
+
+ The path to convert
+
+
+
+ Implicitly convert FileInfo to PathDirectorySingleParameter.
+
+ The path to convert
+
+
+
+ Implicitly convert to FileSystemInfo.
+
+ The path to convert
+
Parameter Class to help automate certificate store lookup but also accept a pure certificate object
@@ -7594,6 +8833,11 @@
Allows integrating PSFramework tab expansion by attribute, rather than command.
+
+
+ The name of the completion
+
+
Create an argument completer offering but the name of the registered completion
@@ -7683,6 +8927,26 @@
Whether to execute the scriptblock in the global scope
+
+
+ If true: Match input against any part of the options, not just the beginning
+
+
+
+
+ If true: Wrap all results into quotes, not just those with whitespace
+
+
+
+
+ If true: Apply FuzzyMatching to the legal completions, not just direct word matching.
+
+
+
+
+ If true: Completion results will not be sorted by name.
+
+
Returns whether a new refresh of tab completion should be executed.
@@ -7694,6 +8958,48 @@
+
+
+ Returns the matching pattern applied against the list of completion values.
+
+ What the user typed so far
+ The resolved pattern to match with
+
+
+
+ The trained values for the current completion scriptblock.
+
+
+
+
+ Whether this completion should automatically be trained with values provided to parameters
+
+
+
+
+ Add a completion option to the list of trained completions.
+
+ The value to offer for completion
+
+
+
+ Add a completion option to the list of trained completions.
+
+ A hashtable with the completion data to add. Must at least contain a "Text" node.
+ An invalid hashtable, not containing a key named "Text" will cause an argument exception
+
+
+
+ Remove a previously provided completion value.
+
+ The text value to no longer complete.
+
+
+
+ Remove a previously provided completion value.
+
+ Hashtable containing the completion to no longer offer.
+
Class that handles the static fields supporting the ÜSFramework TabExpansion implementation
@@ -7709,6 +9015,21 @@
The cache used by scripts utilizing TabExpansionPlusPlus for PSFramework
+
+
+ Whether PSFramework completion should by default match type text to anywhere in the included/valid options' text, not just from the beginning.
+
+
+
+
+ Whether PSFramework completion should always wrap quotes around the completion text, even if there is no whitespace.
+
+
+
+
+ Whether PSFramework completion should use fuzzy-matching when matching completion values with the already typed text.
+
+
Registers a new completion scriptblock
@@ -9917,6 +11238,22 @@
The compressed string to expand
Returns an expanded string.
+
+
+ Joins two path segments together. Will correct path separators, will trace back ".." path segments. Will ignore the Base, if the Child is an absolute Uri.
+
+ The base path to which to add the child
+ The child path segement to add to the base
+ The fully joined path
+
+
+
+ Tool to remove items from an array at a specific location
+
+ The array to remove items from
+ The indexes of the items to remove
+ A new array with all items of the source array, minus the ones at the specified indexes.
+
Returns the execution context for the current runspace based on the current thread.
@@ -10026,6 +11363,15 @@
The arguments to pass to the method
The result of the method invoked
+
+
+ Creates a new PSCustomObject based on the base object wrapped by the input.
+
+ The item to refelct upon.
+ Whether to include public properties / fields
+ Whether to include private properties / fields
+ A new object containing only properties and fields of the base input. Anything else (including noteproperties of the input) will be ignored.
+
Execute a constructor for the specified type, creating a new instance of it.
diff --git a/PSFramework/bin/type-aliases.ps1 b/PSFramework/bin/type-aliases.ps1
index 877a0f6a..7e610543 100644
--- a/PSFramework/bin/type-aliases.ps1
+++ b/PSFramework/bin/type-aliases.ps1
@@ -1,36 +1,44 @@
# Define our type aliases
$TypeAliasTable = @{
- PsfArgumentCompleter = "PSFramework.TabExpansion.PsfArgumentCompleterAttribute"
- PSFComputer = "PSFramework.Parameter.ComputerParameter"
- PSFComputerParameter = "PSFramework.Parameter.ComputerParameter"
- PSFDateTime = "PSFramework.Parameter.DateTimeParameter"
- PSFDateTimeParameter = "PSFramework.Parameter.DateTimeParameter"
- PsfDirectory = 'PSFramework.Parameter.PathDirectoryParameter'
- PsfDirectoryLax = 'PSFramework.Parameter.PathDirectoryLaxParameter'
- PsfDynamicTransform = 'PSFramework.Utility.DynamicTransformationAttribute'
- PSFEncoding = "PSFramework.Parameter.EncodingParameter"
- PSFEncodingParameter = "PSFramework.Parameter.EncodingParameter"
- PsfErrorRecord = 'PSFramework.Meta.PsfErrorRecord'
- PsfFile = 'PSFramework.Parameter.PathFileParameter'
- PsfFileLax = 'PSFramework.Parameter.PathFileLaxParameter'
- PsfHashtable = 'PSFramework.Object.PsfHashtable'
- PsfLiteralPath = 'PSFramework.Parameter.PathLiteralParameter'
- PsfLiteralPathLax = 'PSFramework.Parameter.PathLiteralLaxParameter'
- PsfNewFile = 'PSFramework.Parameter.PathNewFileParameter'
- PSFNumber = 'PSFramework.Utility.Number'
- PsfPath = 'PSFramework.Parameter.PathFileSystemParameter'
- PsfPathLax = 'PSFramework.Parameter.PathFileSystemLaxParameter'
- psfrgx = "PSFramework.Utility.RegexHelper"
- PsfScriptBlock = 'PSFramework.Utility.PsfScriptBlock'
- PsfScriptTransform = 'PSFramework.Utility.ScriptTransformationAttribute'
- PSFSize = "PSFramework.Utility.Size"
- PSFTimeSpan = "PSFramework.Parameter.TimeSpanParameter"
- PSFTimeSpanParameter = "PSFramework.Parameter.TimeSpanParameter"
- PsfValidateLanguageMode = "PSFramework.Validation.PsfValidateLanguageMode"
- PSFValidatePattern = "PSFramework.Validation.PsfValidatePatternAttribute"
- PSFValidatePSVersion = "PSFramework.Validation.PsfValidatePSVersion"
- PSFValidateScript = "PSFramework.Validation.PsfValidateScriptAttribute"
- PSFValidateSet = "PSFramework.Validation.PsfValidateSetAttribute"
+ PsfArgumentCompleter = "PSFramework.TabExpansion.PsfArgumentCompleterAttribute"
+ PSFComputer = "PSFramework.Parameter.ComputerParameter"
+ PSFComputerParameter = "PSFramework.Parameter.ComputerParameter"
+ PSFDateTime = "PSFramework.Parameter.DateTimeParameter"
+ PSFDateTimeParameter = "PSFramework.Parameter.DateTimeParameter"
+ PsfDirectory = 'PSFramework.Parameter.PathDirectoryParameter'
+ PsfDirectoryLax = 'PSFramework.Parameter.PathDirectoryLaxParameter'
+ PsfDirectorySingle = 'PSFramework.Parameter.PathDirectorySingleParameter'
+ PsfDynamicTransform = 'PSFramework.Utility.DynamicTransformationAttribute'
+ PSFEncoding = "PSFramework.Parameter.EncodingParameter"
+ PSFEncodingParameter = "PSFramework.Parameter.EncodingParameter"
+ PsfErrorRecord = 'PSFramework.Meta.PsfErrorRecord'
+ PsfFile = 'PSFramework.Parameter.PathFileParameter'
+ PsfFileLax = 'PSFramework.Parameter.PathFileLaxParameter'
+ PsfFileSingle = 'PSFramework.Parameter.PathFileSingleParameter'
+ PsfHashtable = 'PSFramework.Object.PsfHashtable'
+ PsfLiteralDirectorySingle = 'PSFramework.Parameter.PathLiteralDirectorySingleParameter'
+ PsfLiteralFileSingle = 'PSFramework.Parameter.PathLiteralFileSingleParameter'
+ PsfLiteralPath = 'PSFramework.Parameter.PathLiteralParameter'
+ PsfLiteralPathLax = 'PSFramework.Parameter.PathLiteralLaxParameter'
+ PsfLiteralPathSingle = 'PSFramework.Parameter.PathLiteralSingleParameter'
+ PsfNewFile = 'PSFramework.Parameter.PathNewFileParameter'
+ PsfNewFileSingle = 'PSFramework.Parameter.PathNewFileSingleParameter'
+ PSFNumber = 'PSFramework.Utility.Number'
+ PsfPath = 'PSFramework.Parameter.PathFileSystemParameter'
+ PsfPathLax = 'PSFramework.Parameter.PathFileSystemLaxParameter'
+ PsfPathSingle = 'PSFramework.Parameter.PathFileSystemSingleParameter'
+ psfrgx = "PSFramework.Utility.RegexHelper"
+ PsfScope = 'PSFramework.Meta.Scope'
+ PsfScriptBlock = 'PSFramework.Utility.PsfScriptBlock'
+ PsfScriptTransform = 'PSFramework.Utility.ScriptTransformationAttribute'
+ PSFSize = "PSFramework.Utility.Size"
+ PSFTimeSpan = "PSFramework.Parameter.TimeSpanParameter"
+ PSFTimeSpanParameter = "PSFramework.Parameter.TimeSpanParameter"
+ PsfValidateLanguageMode = "PSFramework.Validation.PsfValidateLanguageMode"
+ PSFValidatePattern = "PSFramework.Validation.PsfValidatePatternAttribute"
+ PSFValidatePSVersion = "PSFramework.Validation.PsfValidatePSVersion"
+ PSFValidateScript = "PSFramework.Validation.PsfValidateScriptAttribute"
+ PSFValidateSet = "PSFramework.Validation.PsfValidateSetAttribute"
}
Set-PSFTypeAlias -Mapping $TypeAliasTable
\ No newline at end of file
diff --git a/PSFramework/changelog.md b/PSFramework/changelog.md
index 0d6e23d5..2728856e 100644
--- a/PSFramework/changelog.md
+++ b/PSFramework/changelog.md
@@ -1,5 +1,68 @@
# CHANGELOG
+## 1.13.406 (2025-08-29)
+
+- New: Assert-PSFInternalCommand - Verifies, that the command calling it in turn was only called from another command within the same module. (#685)
+- New: Add-PSFTeppCompletion - Adds a completion result to a tab completion script.
+- New: Remove-PSFTeppCompletion - Removes a previously added completion result from a tab completion script.
+- New: Update-PSFTeppCompletion - Automatically adds provided values to the completion result of the associated completion script.
+- New: Get-PSFFileContent - Reads content from a file.
+- New: Set-PSFFileContent - Writes content to a file.
+- New: Export-PSFJson - Converts input to json string and writes the result to file.
+- New: Import-PSFJson - Reads json files and returns their content as objects.
+- New: ConvertTo-PSFPsd1 - Converts objects into PSD1 configuration text.
+- New: Export-PSFPowerShellDataFile - Exports data into psd1 config files.
+- New: Register-PSFPsd1Converter - Registers a new piece of logic used to convert specific data types to PSD1 format.
+- New: PSFScope - type granting direct access to the current, script, module or global scope as an object.
+- New: Configuration Setting - `PSFramework.TabExpansion.FuzzyMatch`: Whether to match tab completions with Fuzzy-Matching by default.
+- New: Configuration Setting - `PSFramework.TabExpansion.MatchAnywhere`: Wrap all completion results into quotes, whitespace or not.
+- New: Configuration Setting - `PSFramework.TabExpansion.AlwaysQuote`: Whether to match tab completions with Fuzzy-Matching by default.
+- New: Configuration Setting - `PSFramework.Message.Style.TimeFormat`: The format used in timestamps for messages written on screen.
+- New: Configuration Setting - `PSFramework.Message.Style.Target`: Include the message target (if present) in verbose message output.
+- New: Configuration Export Schema - The "Psd1" schema was added as an alternative name for "MetaJson", offering the same implementation logic.
+- New: Parameter Class PsfFileSingle - Converts input into the path to a single file.
+- New: Parameter Class PsfDirectorySingle - Converts input into the path to a single directory.
+- New: Parameter Class PsfPathSingle - Converts input into the path to a single filesystem path.
+- New: Parameter Class PsfLiteralPathSingle - Converts input into the path to a single filesystem path without resolving wildcards.
+- New: Parameter Class PsfLiteralFileSingle - Converts input into the path to a single file without resolving wildcards.
+- New: Parameter Class PsfLiteralDirectorySingle - Converts input into the path to a single directory without resolving wildcards.
+- New: Parameter Class PsfNewFileSingle - Converts input into the path to a single file, which may or may not exist, but whose parent directory _must_ exist.
+- Upd: Tab Completion - Now sorts by the List Item Text - the options shown in the tab menu - rather than the actual value being inserted into the console.
+- Upd: Tab Completion - Added support for `ToolTipString` and `ListItemTextString` in the result, allowing to localize tab completion tooltip and list items.
+- Upd: Tab Completion - Can now be trained / provided with an explicit list of completions to offer.
+- Upd: Register-PSFTeppScriptblock - add parameter `-FuzzyMatch` to match input against the completions using Fuzzy Logic.
+- Upd: Register-PSFTeppScriptblock - add parameter `-MatchAnywhere` to match input against anywhere within the completions, not just from the start.
+- Upd: Register-PSFTeppScriptblock - add parameter `-AlwaysQuote` to wrap all completion results into quotes, irrespective of whether they contain a whitespace or not.
+- Upd: Register-PSFTeppScriptblock - add parameter `-DontSort` to stop sorting completion results alphabetically.
+- Upd: ConvertTo-PSFHashtable - add parameter `-InheritParameters` that automatically picks up values from the calling command / script.
+- Upd: Write-PSFMessage - add parameter `-NewErrorRecord` to actually write an error when also specifying `-EnableException`, even if the command did not first receive an error record as input. (#659)
+- Upd: Select-PSFObject - Can now select variables directly, as well as properties of variables (#664)
+- Upd: Invoke-PSFProtectedCommand - add parameter `-NonTerminating` to stop sending terminating exceptions in combination with `-EnableException`.
+- Upd: New-PSFHashtable - Added parameter `-PassThru` to create a hashtable that will return the key itself if it does not exist on the hashtable.
+- Upd: New-PSFHashtable - Added parameter `-Calculator` to create a hashtable that will return the result of this scriptblock if the key does not exist on the hashtable.
+- Upd: New-PSFSupportPackage - When specifying a task-name, the command will no longer spam messages on screen, instead sending the message to verbose.
+- Upd: Type Object.ObjectHost - added methods to modify PSObjects in bulk
+- Upd: PSFHashtable - added method `EnablePassThru()`, which will return the Key itself, when the provided key does not yet exist in the hashtable. (#676)
+- Upd: PSFHashtable - added method `SetCalculator()`, which will calculate the value returned using a scriptblock, when the provided key does not yet exist in the hashtable. (#676)
+- Upd: PSFCmdlet - added method `DoContinue()`, allowing cmdlets to trigger a powershell script-based continue action.
+- Upd: Logging Provider logfile - Added option `JsonNoEmptyFirstLine` to make it stop adding an empty line at the beginning of the logfile. As an option to avoid breaking changes for people automatically processing the logfile.
+- Upd: Logging Provider logfile - Added option `CMTraceOverrideComponent` to allow overriding the "Component" section on a per-message basis, vie the `-Data` parameter. (#623)
+- Upd: Logging Provider logfile - Added new filetype `TXT` to the list of supported filetypes. This allows creating simple plaintext logfiles, even when they are generally discouraged. (#654)
+- Upd: Logging Provider logfile - Added option `TXTPattern`: The pattern of any given line in the TXT-based logfile. Use %PROPERTYNAME% as placeholder, e.g. "%Message%". Same properties as with the headers configuration - you need to specify both settings, if your pattern includes non-default properties such as "Data". (#654)
+- Fix: Logging Provider logfile - Renaming header breaks timestamp format (#672)
+- Fix: Logging Provider logfile - Using the `JsonString` parameter breaks header order (#667)
+- Fix: Logging Provider SQL - Fails to log the `Data` field correctly (#680)
+- Fix: Configuration - fails to restore the persisted form of an empty hashtable (#650)
+- Fix: Pathing issue - Programdata resolution fails when environment variable is not available (#671)
+- Fix: ScriptTransformation Attribute - conversion results were not considered valid, when not of the demanded type, but one inheriting from the demanded type. (#646)
+- Fix: ParameterClass Path - Fails on `.ToString()` on Stack Overflow.
+- Fix: Select-PSFObject - fails with "Index was outside the bounds of the array." when combining `-Last` with `-TypeName` or any other parameter that causes the command to intercept the output of `Select-Object` (#666)
+- Fix: ConvertTo-PSFHashtable - fails to correctly resolve the reference command when somebody overrides the Get-Command command in a breaking way.
+- Fix: ConvertTo-PSFHashtable - fails with an index error when the `-ReferenceCommand` command cannot be resolved.
+- Fix: ConvertTo-PSFHashtable - when specifying the `-Inherit` parameter, the `-Exclude` parameter is ignored for inherited values.
+- Fix: Write-PSFMessage - fails to process a Switch value as input. (#655)
+- Fix: Import-PSFPowerShellDataFile - does not import DateTime properties from json correctly on PowerShell 7.
+
## 1.12.346 (2024-09-25)
- Fix: MessageLevel Modifiers break Write-PSFMessage
diff --git a/PSFramework/functions/data/ConvertTo-PSFPsd1.ps1 b/PSFramework/functions/data/ConvertTo-PSFPsd1.ps1
new file mode 100644
index 00000000..6f43f5ce
--- /dev/null
+++ b/PSFramework/functions/data/ConvertTo-PSFPsd1.ps1
@@ -0,0 +1,58 @@
+function ConvertTo-PSFPsd1 {
+ <#
+ .SYNOPSIS
+ Converts objects into PSD1 configuration text.
+
+ .DESCRIPTION
+ Converts objects into PSD1 configuration text.
+
+ Use Register-PSFPsd1Converter to extend/customize how this conversion happens.
+
+ .PARAMETER Depth
+ How many levels deep do you want to process sub-properties?
+ Defaults to 2
+
+ .PARAMETER EnableVerbose
+ Enables deep verbosity when processing objects.
+ By default, individual conversion steps are not tracked for performance reasons.
+ Enable this for extensive amounts of debug messages.
+
+ .PARAMETER Configuration
+ Additional configuration settings to provide for the conversion.
+ Custom converters may use these as implemented in their custom conversion.
+
+ .PARAMETER InputObject
+ The object(s) to convert.
+
+ .EXAMPLE
+ PS C:\> Get-ChildItem | ConvertTo-PSFPsd1
+
+ Takes all files and folders and converts the data into psd1-style data structures.
+ #>
+ [OutputType([string])]
+ [CmdletBinding()]
+ param (
+ [int]
+ $Depth = 2,
+
+ [switch]
+ $EnableVerbose,
+
+ [Hashtable]
+ $Configuration = @{},
+
+ [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
+ [AllowNull()]
+ $InputObject
+ )
+ begin {
+ $converter = [PSFramework.Data.Psd1Converter]::new()
+ $converter.Depth = $Depth
+ $converter.EnableVerbose = $EnableVerbose
+ $converter.Config = $Configuration
+ $converter.Cmdlet = $PSCmdlet
+ }
+ process {
+ $converter.Convert($InputObject)
+ }
+}
\ No newline at end of file
diff --git a/PSFramework/functions/data/Export-PSFJson.ps1 b/PSFramework/functions/data/Export-PSFJson.ps1
new file mode 100644
index 00000000..50a4a524
--- /dev/null
+++ b/PSFramework/functions/data/Export-PSFJson.ps1
@@ -0,0 +1,78 @@
+function Export-PSFJson {
+ <#
+ .SYNOPSIS
+ Converts input to json string and writes the result to file.
+
+ .DESCRIPTION
+ Converts input to json string and writes the result to file.
+ Uses ConvertTo-Json under the hood.
+
+ .PARAMETER Path
+ The path to the file(s) to create.
+ The parent directory must exist, the file will be overwritten if it already does.
+
+ .PARAMETER InputObject
+ The data to convert to json and export.
+
+ .PARAMETER Depth
+ How deep the into sub-properties do we want to delve?
+ Defaults to 2.
+ Any nested sub-properties that are deeper than that many levels in will be lost in the result.
+
+ .PARAMETER Compress
+ Whether the Json string should be compressed to save space.
+
+ .PARAMETER Encoding
+ What encoding to write the file in.
+ Defaults to UTF8 (with BOM).
+
+ .EXAMPLE
+ PS C:\> Get-MgUser | Export-PSFJson .\users.json
+
+ Write all users from Microsoft Graph into a json file.
+ #>
+ [CmdletBinding()]
+ param (
+ [Parameter(Mandatory = $true)]
+ [PSFNewFile]
+ $Path,
+
+ [Parameter(ValueFromPipeline = $true)]
+ $InputObject,
+
+ [int]
+ $Depth,
+
+ [switch]
+ $Compress,
+
+ [PSFArgumentCompleter('PSFramework-Encoding')]
+ [PSFEncoding]
+ $Encoding = 'UTF8'
+ )
+ begin {
+ $convertParam = @{ }
+ if ($PSBoundParameters.Keys -contains 'Depth') { $convertParam.Depth = $Depth }
+ if ($PSBoundParameters.Keys -contains 'Compress') { $convertParam.Compress = $Compress }
+ $converter = { ConvertTo-Json @convertParam }.GetSteppablePipeline()
+
+ # Only needed in END, but for propery validation we open it during begin
+ $exporter = { Set-PSFFileContent -Path $Path -Encoding $Encoding }.GetSteppablePipeline()
+
+ try { $converter.Begin($true) }
+ catch { $PSCmdlet.ThrowTerminatingError($_) }
+
+ try { $exporter.Begin($true) }
+ catch { $PSCmdlet.ThrowTerminatingError($_) }
+ }
+ process {
+ $converter.Process($InputObject)
+ }
+ end {
+ $json = $converter.End()
+ try { $exporter.Process($json) }
+ catch { $PSCmdlet.WriteError($_) }
+
+ $exporter.End()
+ }
+}
\ No newline at end of file
diff --git a/PSFramework/functions/data/Export-PSFPowerShellDataFile.ps1 b/PSFramework/functions/data/Export-PSFPowerShellDataFile.ps1
new file mode 100644
index 00000000..a22ff6a7
--- /dev/null
+++ b/PSFramework/functions/data/Export-PSFPowerShellDataFile.ps1
@@ -0,0 +1,81 @@
+function Export-PSFPowerShellDataFile {
+ <#
+ .SYNOPSIS
+ Exports data into psd1 config files.
+
+ .DESCRIPTION
+ Exports data into psd1 config files.
+
+ Use Register-PSFPsd1Converter to extend/customize how this conversion happens.
+
+ .PARAMETER Path
+ The path where to write to.
+ The parent folder must exist.
+ May provide multiple paths.
+
+ .PARAMETER Depth
+ How many levels deep do you want to process sub-properties?
+ Defaults to 2
+
+ .PARAMETER Encoding
+ The encoding to write the text file in.
+ Defaults to: UTF8 (with BOM).
+
+ .PARAMETER EnableVerbose
+ Enables deep verbosity when processing objects.
+ By default, individual conversion steps are not tracked for performance reasons.
+ Enable this for extensive amounts of debug messages.
+
+ .PARAMETER Configuration
+ Additional configuration settings to provide for the conversion.
+ Custom converters may use these as implemented in their custom conversion.
+
+ .PARAMETER InputObject
+ The object(s) to convert and write to file.
+
+ .EXAMPLE
+ PS C:\> Get-ChildItem | Export-PSFPowerShellDataFile -Path .\files.psd1
+
+ Takes all files and folders and converts the data into psd1-style data structures, then write that to "files.psd1" in the current path..
+ #>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")]
+ [CmdletBinding()]
+ param (
+ [Parameter(Mandatory = $true)]
+ [PSFNewFile]
+ $Path,
+
+ [int]
+ $Depth = 2,
+
+ [PSFArgumentCompleter('PSFramework-Encoding')]
+ [PSFEncoding]
+ $Encoding = 'UTF8',
+
+ [switch]
+ $EnableVerbose,
+
+ [Hashtable]
+ $Configuration = @{},
+
+ [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
+ [AllowNull()]
+ $InputObject
+ )
+ begin {
+ $converter = [PSFramework.Data.Psd1Converter]::new()
+ $converter.Depth = $Depth
+ $converter.EnableVerbose = $EnableVerbose
+ $converter.Config = $Configuration
+ $converter.Cmdlet = $PSCmdlet
+
+ $writer = { Set-PSFFileContent -Path $Path -Encoding $Encoding }.GetSteppablePipeline()
+ $writer.Begin($true)
+ }
+ process {
+ $writer.Process($converter.Convert($InputObject))
+ }
+ end {
+ $writer.End()
+ }
+}
\ No newline at end of file
diff --git a/PSFramework/functions/data/Get-PSFFileContent.ps1 b/PSFramework/functions/data/Get-PSFFileContent.ps1
new file mode 100644
index 00000000..7f3e0995
--- /dev/null
+++ b/PSFramework/functions/data/Get-PSFFileContent.ps1
@@ -0,0 +1,292 @@
+function Get-PSFFileContent {
+ <#
+ .SYNOPSIS
+ Read the contents of a file.
+
+ .DESCRIPTION
+ Read the contents of a file.
+ This is a replacement for Get-Content, trading flexibility in return for focus.
+
+ Notably, it allows for consistent parameterization between PowerShell versions.
+
+ .PARAMETER Path
+ Path to the file(s) to read.
+
+ .PARAMETER LiteralPath
+ Literal Path to the file(s) to read.
+ This does NOT use wildcard expressions when evaluating paths.
+
+ .PARAMETER ReadCount
+ How man lines to include in a single return / dataset.
+ Set to 0 or less to return the entire file as a single string.
+ Example: When reading a file with 13 lines of text, setting "ReadCount" to 5 will return 3 strings: 2 strings of 5 lines each, one with the remaining 3.
+ Defaults to: 1
+
+ .PARAMETER TotalCount
+ How many datasets in total to return.
+ When specified, this allows limiting the returned amount of results.
+ This parameter takes ReadCount into account, not counting individual lines of text, but number of result-sets after considering ReadCount.
+
+ Example 1: File: 50 Lines, ReadCount: 1, TotalCount: 20
+ In this case, the first 20 lines of the text file are returned
+
+ Example 2: File: 50 Lines, ReadCount: 3, TotalCount: 10
+ In this case, the first 10 results of 3-line strings are returned (resulting in 10 strings, covering a total of 30 lines of the text file).
+
+ .PARAMETER Skip
+ How many dataset to skip.
+ The size of a dataset depends on the ReadCount parameter.
+ Skips the first X datasets, unless combined with "Last", in which case it will skip the last X datasets instead.
+
+ .PARAMETER Last
+ Only return the last X datasets from the file.
+ The size of a dataset depends on the ReadCount parameter.
+
+ .PARAMETER AsByteStream
+ Return the content of the file as a binary stream.
+ This parameter changes the behavior of many other parameters, as it is not compatible with "-ReadCount".
+ Each dataset is now always a single byte.
+
+ Example: TotalCount: 48, Skip: 12
+ In this example, it will skip the first 12 bytes in the file and then return the next 48 (or less, if there are fewer bytes in the file in total).
+
+ .PARAMETER Wait
+ Rather than execute the command and then return, wait for some time and keep looking for new entries to be added to the file.
+ This will return new lines / datasets as they are added to the file.
+ This command will look every second for new content in the file.
+
+ .PARAMETER Timeout
+ The amount of time to wait before stopping waiting for new content in the file.
+ Defaults to: 1 hour.
+
+ .PARAMETER Encoding
+ The encoding to interpret the text file under.
+ Has no effect when using "-AsByteStream".
+ Defaults to: UTF8 (with BOM).
+
+ .EXAMPLE
+ PS C:\> Get-PSFFileContent .\response.json | ConvertFrom-Json
+
+ Read a json file and convert into useful objects.
+
+ .EXAMPLE
+ PS C:\> Get-PSFFileContent .\service-2025-08-14.log -Last 20 -Wait
+
+ Read the last 20 lines in the specified logfile, then wait for more lines as they are written to the file.
+
+ .EXAMPLE
+ PS C:\> $certBytes = Get-PSFFileContent -Path .\cert.cer -AsByteStream
+
+ Reads the bytes from the specified certificate file.
+ #>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseOutputTypeCorrectly", "")]
+ [CmdletBinding(DefaultParameterSetName = 'Text')]
+ param (
+ [Parameter(Position = 0, ValueFromPipeline = $true)]
+ [PSFFile]
+ $Path,
+
+ [PSFLiteralPath]
+ $LiteralPath,
+
+ [Parameter(ParameterSetName = 'Text')]
+ [long]
+ $ReadCount = 1,
+
+ [long]
+ $TotalCount,
+
+ [long]
+ $Skip,
+
+ [long]
+ $Last,
+
+ [Parameter(Mandatory = $true, ParameterSetName = 'Bytes')]
+ [switch]
+ $AsByteStream,
+
+ [switch]
+ $Wait,
+
+ [PSFTimeSpan]
+ $Timeout = '1h',
+
+ [Parameter(ParameterSetName = 'Text')]
+ [PSFArgumentCompleter('PSFramework-Encoding')]
+ [PSFEncoding]
+ $Encoding = 'UTF8'
+ )
+ begin {
+ #region Utility Functions
+ function Read-Stream {
+ [CmdletBinding()]
+ param (
+ [System.IO.StreamReader]
+ $Reader,
+
+ [int]
+ $ReadCount,
+
+ [int]
+ $Count,
+
+ [switch]
+ $All,
+
+ [hashtable]
+ $Counter = @{ Total = 0 }
+ )
+
+ $currentCount = 0
+ while (
+ -not $Reader.EndOfStream -and
+ (
+ ($Count -lt 1) -or
+ ($currentCount -lt $Count)
+ ) -and
+ (
+ (-not $Counter.Limit) -or
+ ($Counter.Limit -lt 1) -or
+ ($Counter.Limit -gt $Counter.Total)
+ )
+ ) {
+ $lines = foreach ($index in 1..$ReadCount) {
+ if (-not $Reader.EndOfStream) { $Reader.ReadLine() }
+ }
+ $lines -join "`n"
+ $currentCount++
+ $Counter.Total++
+
+ if (-not $All -and $PSBoundParameters.Keys -notcontains 'Count') { break }
+ }
+ }
+ #endregion Utility Functions
+
+ $first = $true
+ }
+ process {
+ if ($first) {
+ $files = $Path + $LiteralPath | Remove-PSFNull
+ $first = $false
+ }
+ else { $files = $Path }
+
+ :main foreach ($filePath in $files) {
+ #region Binary Read
+ if ($AsByteStream) {
+ try { $fileStream = [System.IO.FileStream]::new($filePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, ([System.IO.FileShare]'ReadWrite, Delete')) }
+ catch {
+ $PSCmdlet.WriteError($_)
+ continue
+ }
+
+ $start = 0
+ $end = $fileStream.Length
+ if ($PSBoundParameters.Keys -contains 'Last') {
+ $start = $end - $Last
+ if ($Skip -gt 0) {
+ $start = $start - $Skip
+ $end = $end - $Skip
+ }
+ }
+ elseif ($PSBoundParameters.Keys -contains 'TotalCount') {
+ $end = $start + $TotalCount
+ if ($Skip -gt 0) {
+ $start = $start + $Skip
+ $end = $end + $Skip
+ }
+ }
+ if ($start -lt 0) { $start = 0 }
+ if ($end -gt $fileStream.Length) { $end = $fileStream.Length }
+ if ($start -eq $end) { continue }
+
+ $length = $end - $start
+
+ $buffer = [byte[]]::new($length)
+
+ try {
+ $fileStream.Position = $Start
+ $null = $fileStream.Read($buffer, 0, $Length)
+ , $buffer
+ }
+ catch { $PSCmdlet.WriteError($_) }
+ finally {
+ $fileStream.Close()
+ $fileStream.Dispose()
+ }
+
+ continue
+ }
+ #endregion Binary Read
+
+ #region Text Read
+ try { $fileStream = [System.IO.FileStream]::new($filePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, ([System.IO.FileShare]'ReadWrite, Delete')) }
+ catch {
+ $PSCmdlet.WriteError($_)
+ continue
+ }
+ $reader = [System.IO.StreamReader]::new($fileStream, $Encoding)
+
+ try {
+ if ($PSBoundParameters.Keys -contains 'Last') {
+ $lines = [PSFramework.Utility.LimitedConcurrentQueue[string]]::new(($Last + $Skip))
+ foreach ($result in Read-Stream -Reader $reader -ReadCount $ReadCount -All) {
+ $lines.Enqueue($result)
+ }
+ $total = $Last - ($lines.Size - $lines.Count)
+ # We skipped more than we had
+ if ($total -lt 1) { continue main }
+
+ @($($lines))[0..($total - 1)]
+ }
+
+ if ($Skip -gt 1) {
+ $null = Read-Stream -Reader $reader -ReadCount $ReadCount -Count $Skip
+ }
+ if ($reader.EndOfStream -and -not $Wait) { continue main }
+
+ $counter = @{
+ Total = 0
+ Limit = $TotalCount
+ }
+
+ if (-not $reader.EndOfStream) {
+ if ($ReadCount -lt 1) {
+ $reader.ReadToEnd().Trim("`n`r")
+ if (-not $Wait) { continue main }
+ }
+ else {
+ Read-Stream -Reader $reader -ReadCount $ReadCount -All -Counter $counter
+ }
+ }
+
+ if (-not $Wait) { continue main }
+
+ $start = Get-Date
+ $timeLimit = $start.Add($Timeout)
+
+ while (
+ (
+ ($TotalCount -le 0) -or
+ ($counter.Total -lt $counter.Limit)
+ ) -and
+ (([datetime]::Now) -lt $timeLimit)
+ ) {
+ if (-not $reader.EndOfStream) {
+ Read-Stream -Reader $reader -ReadCount $ReadCount -All -Counter $counter
+ }
+ Start-Sleep -Seconds 1
+ }
+ }
+ catch {
+ $PSCmdlet.WriteError($_)
+ }
+ finally {
+ $fileStream.Close()
+ $fileStream.Dispose()
+ }
+ #endregion Text Read
+ }
+ }
+}
\ No newline at end of file
diff --git a/PSFramework/functions/data/Import-PSFJson.ps1 b/PSFramework/functions/data/Import-PSFJson.ps1
new file mode 100644
index 00000000..02e3d49e
--- /dev/null
+++ b/PSFramework/functions/data/Import-PSFJson.ps1
@@ -0,0 +1,215 @@
+function Import-PSFJson {
+ <#
+ .SYNOPSIS
+ Imports a json document from file and offers its content as objects.
+
+ .DESCRIPTION
+ Imports a json document from file and offers its content as objects.
+
+ WARNING: The FixData parameter is experimental and may experience breaking changes!
+
+ .PARAMETER Path
+ Path to the file to import.
+ Will evaluate wildcards.
+
+ .PARAMETER LiteralPath
+ Path to the file to import.
+ Will NOT evaluate wildcards.
+
+ .PARAMETER Encoding
+ The encoding of the file to read.
+ Defaults to UTF8.
+
+ .PARAMETER AsHashtable
+ Return content as hashtable, rather than PSCustomObject
+
+ .PARAMETER FixData
+ EXPERIMENTAL PARAMETER, MAY SUFFER BREAKING CHANGES
+ Attempt to fix broken data from the data processed.
+ Assumes the json was originally generated through PowerShell and tries to detect and fix issues that happened during export.
+ Most notably: Timestamps no longer being proper timestamps.
+
+ .EXAMPLE
+ PS C:\> Import-PSFJson .\policies.json
+
+ Reads the content of policies.json, parses its structure and returns objects representing its content.
+ #>
+ [CmdletBinding()]
+ param (
+ [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
+ [PSFFileLax]
+ $Path,
+
+ [PSFLiteralPath]
+ $LiteralPath,
+
+ [PSFArgumentCompleter('PSFramework-Encoding')]
+ [PSFEncoding]
+ $Encoding = 'UTF8',
+
+ [switch]
+ $AsHashtable,
+
+ [switch]
+ $FixData
+ )
+ begin {
+ #region Functions
+ function ConvertTo-Hashtable {
+ [OutputType([hashtable])]
+ [CmdletBinding()]
+ param (
+ [Parameter(ValueFromPipeline = $true)]
+ $InputObject
+ )
+
+ begin {
+ $jsonTypes = @(
+ 'System.String'
+ 'System.Int32'
+ 'System.Int64'
+ 'System.Double'
+ 'System.Bool'
+ 'System.DateTime'
+ )
+ }
+ process {
+ $hashtable = $InputObject | ConvertTo-PSFHashtable
+ foreach ($pair in $hashtable.GetEnumerator()) {
+ if ($null -eq $pair.Value) { continue }
+ if ($pair.Value.GetType().FullName -in $jsonTypes) { continue }
+ if ($pair.Value -is [object[]]) {
+ $pair.Value = foreach ($value in $pair.Value) {
+ if ($null -eq $value) { $null; continue }
+ if ($value.GetType().FullName -in $jsonTypes) { $value; continue }
+ if ($value -is [object[]]) { $value; continue } # Accept not resolving double-nested arrays for simplicity
+ ConvertTo-Hashtable -InputObject $value
+ }
+ continue
+ }
+ $pair.Value = ConvertTo-Hashtable -InputObject $pair.Value
+ }
+ $hashtable
+ }
+ }
+ function Convert-JsonData {
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseOutputTypeCorrectly", "")]
+ [CmdletBinding()]
+ param (
+ [Parameter(ValueFromPipeline = $true)]
+ $Data
+ )
+
+ begin {
+ $jsonTypes = @(
+ 'System.String'
+ 'System.Int32'
+ 'System.Int64'
+ 'System.Double'
+ 'System.Bool'
+ 'System.DateTime'
+ )
+ }
+ process {
+ if ($null -eq $Data) { return }
+
+ if ($Data -is [object[]]) {
+ ,@(ConvertFrom-JsonArray -Data $Data)
+ return
+ }
+
+ if ($Data -is [hashtable]) { $properties = $Data.Keys }
+ else { $properties = $Data.PSObject.Properties.Name }
+
+ foreach ($property in $properties) {
+ $item = $Data.$property
+ if ($null -eq $item) { continue }
+ if ($item -is [string] -and $item -match '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{7}\+\d{2}:\d{2}$') {
+ $Data.$Property = $item -as [datetime]
+ continue
+ }
+ if ($item.GetType().FullName -in $jsonTypes) { continue }
+ if ($item -is [object[]]) {
+ $Data.$property = @(ConvertFrom-JsonArray -Data $item)
+ continue
+ }
+
+ # DateTime vNext
+ if (
+ $($item.PSObject.Properties).Count -eq 3 -and
+ $item.PSObject.Properties.Name -contains 'value' -and
+ $item.PSObject.Properties.Name -contains 'DisplayHint' -and
+ $item.PSObject.Properties.Name -contains 'DateTime' -and
+ $item.value -is [datetime]
+ ) {
+ $Data.$property = $item.value
+ continue
+ }
+
+ $Data.$property = Convert-JsonData -Data $item
+ }
+
+ $Data
+ }
+ }
+ function ConvertFrom-JsonArray {
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseOutputTypeCorrectly", "")]
+ [CmdletBinding()]
+ param (
+ $Data
+ )
+
+ $jsonTypes = @(
+ 'System.String'
+ 'System.Int32'
+ 'System.Double'
+ 'System.Bool'
+ 'System.DateTime'
+ )
+
+ foreach ($item in $Data) {
+ if ($null -eq $item) { $null; continue }
+ if ($item -is [string] -and $item -match '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{7}\+\d{2}:\d{2}$') {
+ $item -as [datetime]
+ continue
+ }
+ if ($item.GetType().FullName -in $jsonTypes) { $item; continue }
+ if ($item -is [object[]]) { ,@(ConvertFrom-JsonArray -Data $item); continue }
+ if (
+ $item.PSObject.Properties.Count -eq 3 -and
+ $item.PSObject.Properties.Name -contains 'value' -and
+ $item.PSObject.Properties.Name -contains 'DisplayHint' -and
+ $item.PSObject.Properties.Name -contains 'DateTime' -and
+ $item.value -is [datetime]
+ ) {
+ $item.value
+ continue
+ }
+ Convert-JsonData -Data $item
+ }
+ }
+ #endregion Functions
+ }
+ process {
+ foreach ($entry in $Path.FailedInput) {
+ Write-Error "Could not resolve as file: $entry"
+ }
+
+ foreach ($filePath in $Path + $LiteralPath) {
+ $content = Get-PSFFileContent -LiteralPath $filePath -Encoding $Encoding
+ if (-not $AsHashtable) { $data = $content | ConvertFrom-Json }
+ else {
+ if ($PSVersionTable.PSVersion.Major -gt 5) { $data = $content | ConvertFrom-Json -AsHashtable }
+ else { $data = $content | ConvertFrom-Json | ConvertTo-Hashtable }
+ }
+
+ if (-not $FixData) {
+ $data
+ continue
+ }
+
+ # Fix Data (expensive)
+ Convert-JsonData -Data $data
+ }
+ }
+}
\ No newline at end of file
diff --git a/PSFramework/functions/utility/Import-PSFPowerShellDataFile.ps1 b/PSFramework/functions/data/Import-PSFPowerShellDataFile.ps1
similarity index 98%
rename from PSFramework/functions/utility/Import-PSFPowerShellDataFile.ps1
rename to PSFramework/functions/data/Import-PSFPowerShellDataFile.ps1
index b8679b82..4fb80f9d 100644
--- a/PSFramework/functions/utility/Import-PSFPowerShellDataFile.ps1
+++ b/PSFramework/functions/data/Import-PSFPowerShellDataFile.ps1
@@ -86,8 +86,10 @@
$jsonTypes = @(
'System.String'
'System.Int32'
+ 'System.Int64'
'System.Double'
'System.Bool'
+ 'System.DateTime'
)
}
process {
@@ -96,12 +98,12 @@
if ($null -eq $pair.Value) { continue }
if ($pair.Value.GetType().FullName -in $jsonTypes) { continue }
if ($pair.Value -is [object[]]) {
- $pair.Value = foreach ($value in $pair.Value) {
+ $pair.Value = @(foreach ($value in $pair.Value) {
if ($null -eq $value) { $null; continue }
if ($value.GetType().FullName -in $jsonTypes) { $value; continue }
if ($value -is [object[]]) { $value; continue } # Accept not resolving double-nested arrays for simplicity
ConvertTo-Hashtable -InputObject $value
- }
+ })
continue
}
$pair.Value = ConvertTo-Hashtable -InputObject $pair.Value
diff --git a/PSFramework/functions/data/Register-PSFPsd1Converter.ps1 b/PSFramework/functions/data/Register-PSFPsd1Converter.ps1
new file mode 100644
index 00000000..5df324dc
--- /dev/null
+++ b/PSFramework/functions/data/Register-PSFPsd1Converter.ps1
@@ -0,0 +1,145 @@
+function Register-PSFPsd1Converter {
+ <#
+ .SYNOPSIS
+ Registers a new piece of logic used to convert specific data types to PSD1 format.
+
+ .DESCRIPTION
+ Registers a new piece of logic used to convert specific data types to PSD1 format.
+ This is used by ConvertTo-PSFPsd1 or Export-PSFPowerShellDataFile.
+
+ There are two fundamental ways to define these extensions:
+ - Provide a data type written in C#
+ - Provide a Scriptblock
+
+ In either way, they target a specific datatype that they will convert.
+ When processing elements in a object into a psd1 document, each element - property value or value of a subproperty - is processed based on its data type.
+ The decision tree is thus processed:
+ - Is it null? Then do '$null'
+ - Is it an Enum? Then do "''"
+ - Do we have a converter defined for this exact type? If so, use it.
+ - Is it a dictionary / hashtable? Then use the predefined converter for that
+ - Do we have a converter defined for a type the input can be assigned to (Parent Type, Interface, etc.)? If so, use it (in case of multiple matches, a random one is picked).
+ - Use the predefined default converter for all other PowerShell objects.
+
+ This means, even if you define a converter for a specific interface, if the object processed is also a dictionary, your converter will be ignored.
+
+ > Data Type written in C#
+ For this, you need to define a class implementing the interface: PSFramework.Data.IPsd1Converter
+ Which essentially requires a single "Convert" method to return the string that becomes part of the PSD1 document.
+
+ > Scriptblock
+ A scriptblock as a converter is a fairly simple thing and allows you to do your conversion in script.
+ The item to convert is found under "$_", the scriptblock must return the result string as output.
+ More details on the parameter "Code"'s documentation.
+ ScriptBlocks offer inherently worse performance than C#-based converters.
+
+ .PARAMETER AssignedType
+ The type that should be converted.
+ The same converter can be assigned to multiple types.
+ Can be either a direct datatype or an interface, affecting all types implementing that type.
+ In case of a direct datatype, classes inheriting from that type will also be affected, unless a direct assignments overrides this.
+
+ .PARAMETER Code
+ The script-code that should convert data into PSD1 string.
+ There are several special considerations when implementing this:
+
+ - The value to convert is in the automatic "$_" variable.
+ - It receives two arguments: "Depth" & "Converter"
+ - Depth is current indentation level, each number representing 4 whitespaces. A Depth of 2 becomes 8 whitespaces.
+ - Converter is the entire conversion runtime and offers some tooling for your scriptblock (covered below)
+ - The automatic "$this" variable contains a hashtable with all details, including a few important properties:
+ - "Parents" are parent objects. This fills up as we delve into sub-properties and sub-properties of sub-properties.
+ The Parents are used to determine depth levels compared to the "MaxDepth" property on the Converter. Use this to determine, whether to truncate your results due to depth being exceeded.
+ It also should be used to prevent infinite recursion - if your current object is already part of the parents, stop this from continuing.
+ - "Converter" is the same thing as the argument received.
+ - "Properties" is a hashtable with settings defined with the script converter. Intended for if you want to provide some data with your conversion scriptblock.
+ - "Depth" is the current indentation depth, which is not necessarily the same as the nested property depth used to determine, when to truncate the result.
+ - "Value" is the exact same thing as what is already available in "$_"
+ - The scriptblock must return a string, and only the first output element will be considered
+
+ > Converter
+ The converter object manages the runtime and offers some settings and methods that may be useful:
+ It has the "Convert" method, which allows you to use the conversion system for some of the properties on your object, while doing your own thing on the others.
+ This method comes with two overloads:
+
+ Convert(System.Object Value)
+ Convert(System.Object Value, System.Object[] Parents, int Depth)
+
+ Generally, you want to use the latter one, to ensure MaxDepth, recursion protection and indentation levels remain consistent.
+
+ It also comes with an "EscapeQuotes" method, that allows you to safely escape single-quotes (and not just the ones on your keyboard, but also typographic/"curly" ones).
+ The "MaxDepth" property gives you access to the maximum nested property depth the user wanted to serialize. It's up to you to actually respect that (or not, as you chose).
+ Note: MaxDepth should not be compared to "Depth" (which is the indentation depth), but rather to the number of items in "Parents".
+
+ Finally, the "Config" property on the converter allows your converter scriptblock to accept config settings at the conversion level, rather than on the converter (which would be constant for _all_ conversions).
+ It is a hashtable users may chose to provide when calling ConvertTo-PSFPsd1 or Export-PSFPowerShellDataFile.
+
+ .PARAMETER DefaultResult
+ The default result to use when the scriptblock runs into a terminating error.
+ Will not actually be applied, unless "OnErrorDefault" is set.
+ Defaults to: '$null'
+
+ .PARAMETER OnErrorDefault
+ When the converter fails with a terminating error, use the default value, rather than kill the conversion.
+ By default, an error during a conversion scriptblock will fail the entire transforamtion.
+
+ .PARAMETER Properties
+ A hashtable of extra information to provide to the conversion scriptblock.
+ This data will be the same in all conversion calls, no matter the object being processed.
+ See the description for the code parameter on how to access per-conversion config settings.
+
+ .PARAMETER Converter
+ The instance of a fully implemented (in c#) Converter.
+ It comes with properties and behaviors as you defined it.
+
+ .EXAMPLE
+ Register-PSFPsd1Converter -Code { "'{0}'" -f $this.Converter.EscapeQuotes($_.ComputerName) } -AssignedType ([PSFramework.Parameter.ComputerParameter])
+
+ Registers a converter that will take the [PSFComputer] parameter class and serialize it into the ComputerName, no matter what data was provided for it.
+ #>
+ [CmdletBinding(DefaultParameterSetName = 'Script')]
+ param (
+ [Parameter(Mandatory = $true)]
+ [Type[]]
+ $AssignedType,
+
+ [Parameter(Mandatory = $true, ParameterSetName = 'Script')]
+ [PsfScriptblock]
+ $Code,
+
+ [Parameter(ParameterSetName = 'Script')]
+ [string]
+ $DefaultResult = '$null',
+
+ [Parameter(ParameterSetName = 'Script')]
+ [switch]
+ $OnErrorDefault,
+
+ [Parameter(ParameterSetName = 'Script')]
+ [hashtable]
+ $Properties,
+
+ [Parameter(Mandatory = $true, ParameterSetName = 'Binary')]
+ [PSFramework.Data.IPsd1Converter]
+ $Converter
+ )
+ process {
+ if ($Converter) {
+ foreach ($assignee in $AssignedType) {
+ [PSFramework.Data.DataHost]::Converters[$assignee] = $Converter
+ }
+ return
+ }
+
+ foreach ($assignee in $AssignedType) {
+ $newConverter = [PSFramework.Data.Converters.CustomConverter]::new(
+ $assignee,
+ $Code
+ )
+ $newConverter.DefaultResult = $DefaultResult
+ $newConverter.OnErrorDefault = $OnErrorDefault
+ $newConverter.Properties = $Properties
+ [PSFramework.Data.DataHost]::Converters[$newConverter.AssignedType] = $newConverter
+ }
+ }
+}
\ No newline at end of file
diff --git a/PSFramework/functions/data/Set-PSFFileContent.ps1 b/PSFramework/functions/data/Set-PSFFileContent.ps1
new file mode 100644
index 00000000..98d9ddb9
--- /dev/null
+++ b/PSFramework/functions/data/Set-PSFFileContent.ps1
@@ -0,0 +1,135 @@
+function Set-PSFFileContent {
+ <#
+ .SYNOPSIS
+ Writes content to a file.
+
+ .DESCRIPTION
+ Writes content to a file.
+ This is a replacement for Set-Content, trading flexibility in return for focus.
+
+ Notably, it allows for consistent parameterization between PowerShell versions.
+
+ .PARAMETER Path
+ The file(s) to write to.
+ Supports multiple files, at least the folder must exist.
+
+ .PARAMETER InputObject
+ The content to write.
+ By default, this content will be converted to string and written as a line per object.
+
+ .PARAMETER AsByteStream
+ Instead of writing the content as a text file, write it as a binary blob.
+ This requires the input to be valid bytes!
+ Note: Writing binary filas via input from pipeline is SIGNIFICANTLY slower than providing the bytes as an explicitly bound parameter.
+
+ .PARAMETER Encoding
+ The encoding to write the text file in.
+ Has no effect when using "-AsByteStream".
+ Defaults to: UTF8 (with BOM).
+
+ .PARAMETER NewLine
+ The symbols to use as NewLine.
+ The usual key consideration here is whether to include the carriage return (`r) or not.
+ Defaults to: "`n"
+ If you wish to include the carriage return, set it to "`r`n".
+
+ .PARAMETER Append
+ Whether to append your content to an existing file.
+ Still creates the file, if it does not exist.
+ By default, all previous content will be overwritten.
+
+ .PARAMETER NoFlush
+ Do not flush the contents of the files to disk, as you write them.
+ By default, all contents - lines or bytes - are immediately written to disk ("Flushed"), to ensure no data is lost, in case of the pipeline failing.
+ This increase in data assurance comes at an increased performance cost.
+ Setting this parameter means it will only at the end, when all content has been sent, flush the contents to disk.
+
+ .EXAMPLE
+ PS C:\> Get-MgUser | ConvertTo-Json | Set-PSFFileContent -Path .\users.json
+
+ Writes all users in the tenant as json to disk.
+
+ .EXAMPLE
+ PS C:\> $cert.GetRawCertData() | Set-PSFFileContent -Path .\cert.cer -AsByteStream
+
+ Writes the raw certificate information as a .cer file to disk.
+ This will result in a perfectly valid public certificate.
+ #>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
+ [CmdletBinding()]
+ param (
+ [Parameter(Mandatory = $true)]
+ [PSFNewFile]
+ $Path,
+
+ [Parameter(ValueFromPipeline = $true)]
+ [AllowEmptyString()]
+ [object[]]
+ $InputObject,
+
+ [switch]
+ $AsByteStream,
+
+ [PSFArgumentCompleter('PSFramework-Encoding')]
+ [PSFEncoding]
+ $Encoding = 'UTF8',
+
+ [string]
+ $NewLine = "`n",
+
+ [switch]
+ $Append,
+
+ [switch]
+ $NoFlush
+ )
+ begin {
+ $mode = 'Create'
+ if ($Append) { $mode = 'OpenOrCreate' }
+
+ try {
+ $writers = foreach ($filePath in $Path) {
+ $fileStream = [System.IO.FileStream]::new($filePath, $mode, 'ReadWrite', 'Read')
+ if ($Append) { $fileStream.Position = $fileStream.Length }
+ if ($AsByteStream) { $fileStream; continue }
+
+ $writer = [System.IO.StreamWriter]::new($fileStream, $Encoding)
+ if (-not $NoFlush) { $writer.AutoFlush = $true }
+ $writer.NewLine = $NewLine
+ $writer
+ }
+ }
+ catch {
+ $PSCmdlet.ThrowTerminatingError($_)
+ }
+ }
+ process {
+ if (-not $AsByteStream) {
+ foreach ($inputItem in $InputObject) {
+ foreach ($writer in $writers) {
+ try { $writer.WriteLine($inputItem) }
+ catch { $PSCmdlet.WriteError($_) }
+ }
+ }
+ }
+ else {
+ try { $bytes = [byte[]]$InputObject }
+ catch {
+ Write-Error "Not a byte-array: $InputObject! Cannot write as byte-stream! $_" -TargetObject $InputObject
+ return
+ }
+ foreach ($writer in $writers) {
+ try { $writer.Write($bytes, 0, $bytes.Length) }
+ catch { $PSCmdlet.WriteError($_) }
+ if (-not $NoFlush) { $writer.Flush() }
+ }
+ }
+ }
+ end {
+ foreach ($writer in $writers) {
+ $writer.Flush()
+ $writer.Close()
+ $writer.Dispose()
+ }
+ }
+}
\ No newline at end of file
diff --git a/PSFramework/functions/tabexpansion/Add-PSFTeppCompletion.ps1 b/PSFramework/functions/tabexpansion/Add-PSFTeppCompletion.ps1
new file mode 100644
index 00000000..7161d06c
--- /dev/null
+++ b/PSFramework/functions/tabexpansion/Add-PSFTeppCompletion.ps1
@@ -0,0 +1,53 @@
+function Add-PSFTeppCompletion {
+ <#
+ .SYNOPSIS
+ Adds a completion result to a tab completion script.
+
+ .DESCRIPTION
+ Adds a completion result to a tab completion script.
+ This allows specifically adding individual values to provide when completing, no matter the actual completion logic.
+ Use Register-PSFTeppScriptblock to define a new completion scriptblock.
+
+ .PARAMETER Name
+ Name of the tab completion scriptblock to add to.
+ Use Register-PSFTeppScriptblock to define a new completion scriptblock.
+
+ .PARAMETER Options
+ The completion objects to provide.
+ Provide either a basic string or a hashtable with some key/value pairs:
+ - Text: Mandatory. The text to complete.
+ - ToolTip: A friendly text to provide some context when completing using CTRL+Space.
+ - ListItemText: The text to show in the completion menu, but different from the actual text inserted.
+ - ToolTipString: A localization key to resolve into the currently configured language for the ToolTip.
+ - ListItemTextString: A localization key to resolve into the currently configured language for the ListItemText.
+
+ .EXAMPLE
+ PS C:\> Add-PSFTeppCompletion -Name 'Alcohol.Type' -Options Wine, Beer, Vodka
+
+ Adds these options to the specified completion results: Wine, Beer, Vodka
+
+ .EXAMPLE
+ PS C:\> Add-PSFTeppCompletion -Name 'Alcohol.Type' -Options @{ Text = 'Mead'; ToolTip = 'Elixir of the angry gods' }
+
+ Add a completion to the completer named "Alcohol.Type", offering the text "Mead" and explaining it with the specified tooltip.
+ #>
+ [CmdletBinding()]
+ param (
+ [PsfArgumentCompleter('PSFramework-tepp-scriptblockname')]
+ [PsfValidateSet(TabCompletion = 'PSFramework-tepp-scriptblockname')]
+ [Parameter(Mandatory = $true)]
+ [string]
+ $Name,
+
+ [Parameter(Mandatory = $true)]
+ [object[]]
+ $Options
+ )
+ process {
+ $completionScript = [PSFramework.TabExpansion.TabExpansionHost]::Scripts[$Name]
+ foreach ($option in $Options) {
+ if ($option -is [string]) { $completionScript.AddTraining($option) }
+ else { $completionScript.AddTraining(($option | ConvertTo-PSFHashtable)) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/PSFramework/functions/tabexpansion/Get-PSFTeppCompletion.ps1 b/PSFramework/functions/tabexpansion/Get-PSFTeppCompletion.ps1
new file mode 100644
index 00000000..24e018da
--- /dev/null
+++ b/PSFramework/functions/tabexpansion/Get-PSFTeppCompletion.ps1
@@ -0,0 +1,52 @@
+function Get-PSFTeppCompletion {
+ <#
+ .SYNOPSIS
+ Lists the registered completion options.
+
+ .DESCRIPTION
+ Lists the registered completion options.
+ Using Add-PSFTeppCompletion, it is possible to manually provide values that will be offered during tab completion for a given argument completion script.
+ Alternatively, a completion scriptblock can be configured for "AutoTraining" during setup via "Register-PSFTeppScriptblock", which enables automatically
+ remembering values previously provided (By later calling Update-PSFTeppCompletion).
+
+ In either case, those values are stored in memory and retrieved using this command.
+
+ .PARAMETER Name
+ Name of the completer scriptblock, for which to retrieve registered options.
+ Defaults to *
+
+ .EXAMPLE
+ PS C:\> Get-PSFTeppCompletion
+
+ List all registered completion options for all completer scriptblocks.
+ #>
+ [CmdletBinding()]
+ param (
+ [PsfArgumentCompleter('PSFramework-tepp-scriptblockname')]
+ [Parameter(ValueFromPipeline = $true)]
+ [string[]]
+ $Name = '*'
+ )
+ begin {
+ $completerScripts = [PSFramework.TabExpansion.TabExpansionHost]::Scripts.Values
+ $processed = @{ }
+ }
+ process {
+ foreach ($entry in $Name) {
+ foreach ($completerScript in $completerScripts) {
+ if ($processed[$completerScript]) { continue }
+ if ($completerScript.Name -notlike $entry) { continue }
+
+ $processed[$completerScript] = $completerScript
+
+ foreach ($completionResult in $completerScript.Trained) {
+ if (-not $completionResult) { continue }
+ $newResult = $completionResult.Clone()
+ $newResult.PSTypeName = 'PSFramework.TabExpansion.CompletionData'
+ $newResult.Completion = $completerScript.Name
+ [PSCustomObject]$newResult
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/PSFramework/functions/tabexpansion/New-PSFTeppCompletionResult.ps1 b/PSFramework/functions/tabexpansion/New-PSFTeppCompletionResult.ps1
index fc6af2fe..7e26b768 100644
--- a/PSFramework/functions/tabexpansion/New-PSFTeppCompletionResult.ps1
+++ b/PSFramework/functions/tabexpansion/New-PSFTeppCompletionResult.ps1
@@ -19,6 +19,9 @@
.PARAMETER CompletionResultType
The type of object that is being completed.
By default it generates one of type paramter value.
+
+ .PARAMETER AlwaysQuote
+ Always place quotes around results, whether the text has a whitespace or not.
.PARAMETER NoQuotes
Whether to put the result in quotes or not.
@@ -45,6 +48,9 @@
[System.Management.Automation.CompletionResultType]
$CompletionResultType = [System.Management.Automation.CompletionResultType]::ParameterValue,
+
+ [switch]
+ $AlwaysQuote,
[switch]
$NoQuotes
@@ -61,7 +67,10 @@
# not be included, via the -NoQuotes parameter,
# then skip adding quotes.
- if ($CompletionResultType -eq [System.Management.Automation.CompletionResultType]::ParameterValue -and -not $NoQuotes)
+ if ($AlwaysQuote -and $CompletionText -notmatch '^".+"$' -and $CompletionText -notmatch "^'.+'$") {
+ $CompletionText = "'$($CompletionText -replace "'","''")'"
+ }
+ elseif ($CompletionResultType -eq [System.Management.Automation.CompletionResultType]::ParameterValue -and -not $NoQuotes)
{
# Add single quotes for the caller in case they are needed.
# We use the parser to robustly determine how it will treat
diff --git a/PSFramework/functions/tabexpansion/Register-PSFTeppScriptblock.ps1 b/PSFramework/functions/tabexpansion/Register-PSFTeppScriptblock.ps1
index c75947a7..11aacccf 100644
--- a/PSFramework/functions/tabexpansion/Register-PSFTeppScriptblock.ps1
+++ b/PSFramework/functions/tabexpansion/Register-PSFTeppScriptblock.ps1
@@ -39,6 +39,22 @@
.PARAMETER Global
Whether the scriptblock should be executed in the global context.
This parameter is needed to reliably execute in background runspaces, but means no direct access to module content.
+
+ .PARAMETER MatchAnywhere
+ Match input against any part of the completion text, not just the beginning.
+
+ .PARAMETER FuzzyMatch
+ Apply FuzzyMatching to the legal completion text, not just direct word matching.
+
+ .PARAMETER AlwaysQuote
+ All completion results will be wrapped in quotes, not just the ones with a whitespace.
+
+ .PARAMETER DontSort
+ Completion results are no longer sorted alphabetically.
+
+ .PARAMETER AutoTraining
+ Automatically train the tab completion by caching user inputs.
+ Requires using Update-PSFTeppCompletion inside of the respective command, to automatically pick up new values.
.EXAMPLE
Register-PSFTeppScriptblock -Name "psalcohol-liquids" -ScriptBlock { "beer", "mead", "wine", "vodka", "whiskey", "rum" }
@@ -75,11 +91,32 @@
$CacheDuration = 0,
[switch]
- $Global
+ $Global,
+
+ [switch]
+ $MatchAnywhere,
+
+ [switch]
+ $FuzzyMatch,
+
+ [switch]
+ $AlwaysQuote,
+
+ [switch]
+ $DontSort,
+
+ [switch]
+ $AutoTraining
)
process
{
[PSFramework.TabExpansion.TabExpansionHost]::RegisterCompletion($Name, $ScriptBlock, $Mode, $CacheDuration, $Global)
+ $scriptContainer = [PSFramework.TabExpansion.TabExpansionHost]::Scripts[$Name]
+ if ($PSBoundParameters.Keys -contains 'MatchAnywhere') { $scriptContainer.MatchAnywhere = $MatchAnywhere }
+ if ($PSBoundParameters.Keys -contains 'FuzzyMatch') { $scriptContainer.MatchAnywhere = $FuzzyMatch }
+ if ($PSBoundParameters.Keys -contains 'AlwaysQuote') { $scriptContainer.MatchAnywhere = $AlwaysQuote }
+ if ($PSBoundParameters.Keys -contains 'DontSort') { $scriptContainer.MatchAnywhere = $DontSort }
+ if ($PSBoundParameters.Keys -contains 'AutoTraining') { $scriptContainer.AutoTraining = $AutoTraining }
}
}
diff --git a/PSFramework/functions/tabexpansion/Remove-PSFTeppCompletion.ps1 b/PSFramework/functions/tabexpansion/Remove-PSFTeppCompletion.ps1
new file mode 100644
index 00000000..53dc2a81
--- /dev/null
+++ b/PSFramework/functions/tabexpansion/Remove-PSFTeppCompletion.ps1
@@ -0,0 +1,46 @@
+function Remove-PSFTeppCompletion {
+ <#
+ .SYNOPSIS
+ Removes a previously added completion result from a tab completion script.
+
+ .DESCRIPTION
+ Removes a previously added completion result from a tab completion script.
+ These can be added using Add-PSFTeppCompletion or trained using Import-PSFTeppCompletion.
+ This command has no effect on automatically calculated tab completions!
+
+ .PARAMETER Name
+ Name of the tab completion scriptblock to remove from.
+ Use Register-PSFTeppScriptblock to define a new completion scriptblock.
+
+ .PARAMETER Options
+ The completion options to remove.
+ Must be either the string value of the completion or a hashtable with the "Text" key containing the completion value.
+
+ .EXAMPLE
+ PS C:\> Remove-PSFTeppCompletion -Name 'Alcohol.Type' -Options 'Mojito', 'Caipirinha'
+
+ Removes the two listed drinks from the list of legal completions.
+ #>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
+ [CmdletBinding()]
+ param (
+ [PsfArgumentCompleter('PSFramework-tepp-scriptblockname')]
+ [PsfValidateSet(TabCompletion = 'PSFramework-tepp-scriptblockname')]
+ [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
+ [Alias('Completion')]
+ [string]
+ $Name,
+
+ [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
+ [object[]]
+ $Options
+ )
+
+ process {
+ $completionScript = [PSFramework.TabExpansion.TabExpansionHost]::Scripts[$Name]
+ foreach ($option in $Options) {
+ if ($option -is [string]) { $completionScript.RemoveTraining($option) }
+ else { $completionScript.RemoveTraining($option.Text) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/PSFramework/functions/utility/New-PSFHashtable.ps1 b/PSFramework/functions/utility/New-PSFHashtable.ps1
index 20c4d725..781f47af 100644
--- a/PSFramework/functions/utility/New-PSFHashtable.ps1
+++ b/PSFramework/functions/utility/New-PSFHashtable.ps1
@@ -26,6 +26,13 @@
.PARAMETER DefaultValue
The default value returned by the hashtable, when resolving a key not specified on the hashtable.
+
+ .PARAMETER PassThru
+ When resolving a key not specified on the hashtable, return the key instead of nothing.
+
+ .PARAMETER Calculator
+ When resolving a key not specified on the hashtable, use this logic to determine the result.
+ Receives the key as its sole input / argument / $_
.EXAMPLE
PS C:\> New-PSFHashtable
@@ -42,22 +49,47 @@
PS C:\> New-PSFHashtable -Hashtable $originHash -DefaultValue $false
Returns a PSFHashtable that is a copy of the hashtable in $originHash, which will by default return $false when resolving undefined keys.
+
+ .EXAMPLE
+ PS C:\> New-PSFHashtable -Hashtable $originHash -PassThru
+
+ Returns a PSFHashtable that is a copy of the hashtable in $originHash, which will by default return the key itself when resolving undefined keys.
+
+ .EXAMPLE
+ PS C:\> New-PSFHashtable -Calculator { (Get-Volume | Sort-Object SizeRemaining -Descending)[0] }
+
+ Returns an empty PSFHashtable which will by default return the volume with the most space remaining.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
[OutputType([PSFramework.Object.PsfHashtable])]
- [CmdletBinding()]
+ [CmdletBinding(DefaultParameterSetName = 'Default')]
param (
[Hashtable]
$Hashtable = @{ },
+ [Parameter(ParameterSetName = 'Default')]
[object]
- $DefaultValue
+ $DefaultValue,
+
+ [Parameter(ParameterSetName = 'PassThru')]
+ [switch]
+ $PassThru,
+
+ [Parameter(ParameterSetName = 'Calculator')]
+ [scriptblock]
+ $Calculator
)
process {
$result = [PSFramework.Object.PsfHashtable]::new($Hashtable)
if ($PSBoundParameters.Keys -contains 'DefaultValue') {
$result.SetDefaultValue($DefaultValue)
}
+ if ($PassThru) {
+ $result.EnablePassthru()
+ }
+ if ($Calculator) {
+ $result.SetCalculator($Calculator)
+ }
$result
}
}
\ No newline at end of file
diff --git a/PSFramework/functions/utility/New-PSFSupportPackage.ps1 b/PSFramework/functions/utility/New-PSFSupportPackage.ps1
index 0146f501..1e67f416 100644
--- a/PSFramework/functions/utility/New-PSFSupportPackage.ps1
+++ b/PSFramework/functions/utility/New-PSFSupportPackage.ps1
@@ -199,32 +199,39 @@
$filePathXml = Join-Path -Path $outputPath -ChildPath "powershell_support_pack_$(Get-Date -Format "yyyy_MM_dd-HH_mm_ss").cliDat"
}
$filePathZip = $filePathXml -replace "\.cliDat$", ".zip"
+
+ $messageLevel = 'Important'
+ $headerLevel = 'Critical'
+ if ($TaskName) {
+ $messageLevel = 'Verbose'
+ $headerLevel = 'Verbose'
+ }
}
process
{
if (Test-PSFFunctionInterrupt) { return }
- Write-PSFMessage -Level Critical -String 'New-PSFSupportPackage.Header' -StringValues $filePathZip, (Get-PSFConfigValue -FullName 'psframework.supportpackage.contactmessage' -Fallback '')
+ Write-PSFMessage -Level $headerLevel -String 'New-PSFSupportPackage.Header' -StringValues $filePathZip, (Get-PSFConfigValue -FullName 'psframework.supportpackage.contactmessage' -Fallback '')
$hash = @{ }
if (($Include -band 1) -and -not ($Exclude -band 1))
{
- Write-PSFMessage -Level Important -String 'New-PSFSupportPackage.Messages'
+ Write-PSFMessage -Level $messageLevel -String 'New-PSFSupportPackage.Messages'
$hash["Messages"] = Get-PSFMessage
}
if (($Include -band 2) -and -not ($Exclude -band 2))
{
- Write-PSFMessage -Level Important -String 'New-PSFSupportPackage.MsgErrors'
+ Write-PSFMessage -Level $messageLevel -String 'New-PSFSupportPackage.MsgErrors'
$hash["Errors"] = Get-PSFMessage -Errors
}
if (($Include -band 4) -and -not ($Exclude -band 4))
{
- Write-PSFMessage -Level Important -String 'New-PSFSupportPackage.ConsoleBuffer'
+ Write-PSFMessage -Level $messageLevel -String 'New-PSFSupportPackage.ConsoleBuffer'
$hash["ConsoleBuffer"] = Get-ShellBuffer
}
if (($Include -band 8) -and -not ($Exclude -band 8))
{
- Write-PSFMessage -Level Important -String 'New-PSFSupportPackage.OperatingSystem'
+ Write-PSFMessage -Level $messageLevel -String 'New-PSFSupportPackage.OperatingSystem'
$hash["OperatingSystem"] = if ($IsLinux -or $IsMacOs)
{
[PSCustomObject]@{
@@ -244,12 +251,12 @@
{
$hash["CPU"] = if ($IsLinux -and (Test-Path -Path /proc/cpuinfo))
{
- Write-PSFMessage -Level Important -String 'New-PSFSupportPackage.CPU' -StringValues '/proc/cpuinfo'
+ Write-PSFMessage -Level $messageLevel -String 'New-PSFSupportPackage.CPU' -StringValues '/proc/cpuinfo'
Get-Content -Raw -Path /proc/cpuinfo
}
else
{
- Write-PSFMessage -Level Important -String 'New-PSFSupportPackage.CPU' -StringValues Win32_Processor
+ Write-PSFMessage -Level $messageLevel -String 'New-PSFSupportPackage.CPU' -StringValues Win32_Processor
Get-CimInstance -ClassName Win32_Processor
}
}
@@ -257,48 +264,48 @@
{
$hash["Ram"] = if ($IsLinux -and (Test-Path -Path /proc/meminfo))
{
- Write-PSFMessage -Level Important -String 'New-PSFSupportPackage.RAM' -StringValues '/proc/meminfo'
+ Write-PSFMessage -Level $messageLevel -String 'New-PSFSupportPackage.RAM' -StringValues '/proc/meminfo'
Get-Content -Raw -Path /proc/meminfo
}
else
{
- Write-PSFMessage -Level Important -String 'New-PSFSupportPackage.RAM' -StringValues Win32_PhysicalMemory
+ Write-PSFMessage -Level $messageLevel -String 'New-PSFSupportPackage.RAM' -StringValues Win32_PhysicalMemory
Get-CimInstance -ClassName Win32_PhysicalMemory
}
}
if (($Include -band 64) -and -not ($Exclude -band 64))
{
- Write-PSFMessage -Level Important -String 'New-PSFSupportPackage.PSVersion'
+ Write-PSFMessage -Level $messageLevel -String 'New-PSFSupportPackage.PSVersion'
$hash["PSVersion"] = $PSVersionTable
}
if (($Include -band 128) -and -not ($Exclude -band 128))
{
- Write-PSFMessage -Level Important -String 'New-PSFSupportPackage.History'
+ Write-PSFMessage -Level $messageLevel -String 'New-PSFSupportPackage.History'
$hash["History"] = Get-History
}
if (($Include -band 256) -and -not ($Exclude -band 256))
{
- Write-PSFMessage -Level Important -String 'New-PSFSupportPackage.Modules'
+ Write-PSFMessage -Level $messageLevel -String 'New-PSFSupportPackage.Modules'
$hash["Modules"] = Get-Module
}
if ((($Include -band 512) -and -not ($Exclude -band 512)) -and ($PSVersionTable.PSVersion.Major -le 5))
{
- Write-PSFMessage -Level Important -String 'New-PSFSupportPackage.Snapins'
+ Write-PSFMessage -Level $messageLevel -String 'New-PSFSupportPackage.Snapins'
$hash["SnapIns"] = Get-PSSnapin
}
if (($Include -band 1024) -and -not ($Exclude -band 1024))
{
- Write-PSFMessage -Level Important -String 'New-PSFSupportPackage.Assemblies'
+ Write-PSFMessage -Level $messageLevel -String 'New-PSFSupportPackage.Assemblies'
$hash["Assemblies"] = [appdomain]::CurrentDomain.GetAssemblies() | Select-Object CodeBase, FullName, Location, ImageRuntimeVersion, GlobalAssemblyCache, IsDynamic
}
if (Test-PSFParameterBinding -ParameterName "Variables")
{
- Write-PSFMessage -Level Important -String 'New-PSFSupportPackage.Variables' -StringValues ($Variables -join ", ")
+ Write-PSFMessage -Level $messageLevel -String 'New-PSFSupportPackage.Variables' -StringValues ($Variables -join ", ")
$hash["Variables"] = $Variables | Get-Variable -ErrorAction Ignore
}
if (($Include -band 2048) -and -not ($Exclude -band 2048) -and (-not $ExcludeError))
{
- Write-PSFMessage -Level Important -String 'New-PSFSupportPackage.PSErrors'
+ Write-PSFMessage -Level $messageLevel -String 'New-PSFSupportPackage.PSErrors'
$hash["PSErrors"] = @()
foreach ($errorItem in $global:Error) { $hash["PSErrors"] += New-Object PSFramework.Message.PsfException($errorItem) }
}
@@ -306,14 +313,14 @@
{
if (Test-Path function:Get-DbatoolsLog)
{
- Write-PSFMessage -Level Important -String 'New-PSFSupportPackage.DbaTools.Messages'
+ Write-PSFMessage -Level $messageLevel -String 'New-PSFSupportPackage.DbaTools.Messages'
$hash["DbatoolsMessages"] = Get-DbatoolsLog
- Write-PSFMessage -Level Important -String 'New-PSFSupportPackage.DbaTools.Errors'
+ Write-PSFMessage -Level $messageLevel -String 'New-PSFSupportPackage.DbaTools.Errors'
$hash["DbatoolsErrors"] = Get-DbatoolsLog -Errors
}
foreach ($pair in $script:supportDataProviders.GetEnumerator()) {
- Write-PSFMessage -Level Important -String 'New-PSFSupportPackage.Extension.Collecting' -StringValues $pair.Key
+ Write-PSFMessage -Level $messageLevel -String 'New-PSFSupportPackage.Extension.Collecting' -StringValues $pair.Key
try { $hash["_$($pair.Key)"] = & $pair.Value }
catch {
Write-PSFMessage -Level Warning -String 'New-PSFSupportPackage.Extension.Collecting.Failed' -StringValues $pair.Key -ErrorRecord $_
diff --git a/PSFramework/internal/configurations/message.ps1 b/PSFramework/internal/configurations/message.ps1
index 2afa7713..6457426e 100644
--- a/PSFramework/internal/configurations/message.ps1
+++ b/PSFramework/internal/configurations/message.ps1
@@ -16,8 +16,10 @@ Set-PSFConfig -Module PSFramework -Name 'Developer.Mode.Enable' -Value $false -I
Set-PSFConfig -Module PSFramework -Name 'Message.Style.Breadcrumbs' -Value $false -Initialize -Validation "bool" -Handler { [PSFramework.Message.MessageHost]::EnableMessageBreadcrumbs = $_ } -Description "Controls how messages are displayed. Enables Breadcrumb display, showing the entire callstack. Takes precedence over command name display."
Set-PSFConfig -Module PSFramework -Name 'Message.Style.FunctionName' -Value $true -Initialize -Validation "bool" -Handler { [PSFramework.Message.MessageHost]::EnableMessageDisplayCommand = $_ } -Description "Controls how messages are displayed. Enables command name, showing the name of the writing command. Is overwritten by enabling breadcrumbs."
Set-PSFConfig -Module PSFramework -Name 'Message.Style.Timestamp' -Value $true -Initialize -Validation "bool" -Handler { [PSFramework.Message.MessageHost]::EnableMessageTimestamp = $_ } -Description "Controls how messages are displayed. Enables timestamp display, including a timestamp in each message."
+Set-PSFConfig -Module PSFramework -Name 'Message.Style.Target' -Value $false -Initialize -Validation "bool" -Handler { [PSFramework.Message.MessageHost]::EnableMessageTarget = $_ } -Description "Controls how messages are displayed. Include the message target (if present) in verbose message output."
Set-PSFConfig -Module PSFramework -Name 'Message.Style.Level' -Value $false -Initialize -Validation "bool" -Handler { [PSFramework.Message.MessageHost]::EnableMessageLevel = $_ } -Description "Controls how messages are displayed. Enables level display, including its level in each message."
Set-PSFConfig -Module PSFramework -Name 'Message.Style.NoColor' -Value $false -Initialize -Validation "bool" -Handler { [PSFramework.Message.MessageHost]::NoColor = $_ } -Description "Controls how messages are displayed. Disables colorization of the messages shown on screen. This prevents messages being broken into multiple lines on some agent system such as the universal console or Azure DevOps logs."
+Set-PSFConfig -Module PSFramework -Name 'Message.Style.TimeFormat' -Value 'HH:mm:ss' -Initialize -Validation "string" -Handler { [PSFramework.Message.MessageHost]::TimeFormat = $_ } -Description "Controls how messages are displayed. The format used in timestamps for messages written on screen"
Set-PSFConfig -Module PSFramework -Name 'Message.Style.Prefix' -Value $false -Initialize -Validation "bool" -Handler { [PSFramework.Message.MessageHost]::EnableMessagePrefix = $_ } -Description "Controls how messages are displayed. Enables message prefix display, including a prefix in each message."
Set-PSFConfig -Module PSFramework -Name 'Message.Style.Prefix.Error' -Value "##vso[task.logissue type=error;]" -Initialize -Validation "string" -Handler { [PSFramework.Message.MessageHost]::PrefixValueError = $_ } -Description "Prefix value to use when the level is Warning and the tag 'error' is supplied."
diff --git a/PSFramework/internal/configurations/tabexpansion.ps1 b/PSFramework/internal/configurations/tabexpansion.ps1
new file mode 100644
index 00000000..c85fad9b
--- /dev/null
+++ b/PSFramework/internal/configurations/tabexpansion.ps1
@@ -0,0 +1,4 @@
+# Settings around the Tab Expansion Experience
+Set-PSFConfig -Module PSFramework -Name 'TabExpansion.FuzzyMatch' -Value $false -Initialize -Handler { [PSFramework.TabExpansion.TabExpansionHost]::FuzzyMatch = $args[0] } -Validation 'bool' -Description 'Whether to match tab completions with Fuzzy-Matching by default.'
+Set-PSFConfig -Module PSFramework -Name 'TabExpansion.AlwaysQuote' -Value $false -Initialize -Handler { [PSFramework.TabExpansion.TabExpansionHost]::AlwaysQuote = $args[0] } -Validation 'bool' -Description 'Wrap all completion results into quotes, whitespace or not.'
+Set-PSFConfig -Module PSFramework -Name 'TabExpansion.MatchAnywhere' -Value $false -Initialize -Handler { [PSFramework.TabExpansion.TabExpansionHost]::MatchAnywhere = $args[0] } -Validation 'bool' -Description 'Whether to match tab completions with Fuzzy-Matching by default.'
\ No newline at end of file
diff --git a/PSFramework/internal/configurationschemata/metajson.ps1 b/PSFramework/internal/configurationschemata/metajson.ps1
index 79cd0bea..a18ffe4e 100644
--- a/PSFramework/internal/configurationschemata/metajson.ps1
+++ b/PSFramework/internal/configurationschemata/metajson.ps1
@@ -1,4 +1,4 @@
-Register-PSFConfigSchema -Name MetaJson -Schema {
+$code = {
param (
[string]
$Resource,
@@ -311,4 +311,6 @@
return
}
}
-}
\ No newline at end of file
+}
+Register-PSFConfigSchema -Name MetaJson -Schema $code
+Register-PSFConfigSchema -Name Psd1 -Schema $code
\ No newline at end of file
diff --git a/PSFramework/internal/loggingProviders/logfile.provider.ps1 b/PSFramework/internal/loggingProviders/logfile.provider.ps1
index 5358e9bb..9acbb4e0 100644
--- a/PSFramework/internal/loggingProviders/logfile.provider.ps1
+++ b/PSFramework/internal/loggingProviders/logfile.provider.ps1
@@ -15,16 +15,16 @@
)
$hash = @{
- '%date%' = (Get-Date -Format 'yyyy-MM-dd')
- '%dayofweek%' = (Get-Date).DayOfWeek
- '%day%' = (Get-Date).Day
- '%hour%' = (Get-Date).Hour
- '%minute%' = (Get-Date).Minute
- '%username%' = $env:USERNAME
- '%userdomain%' = $env:USERDOMAIN
+ '%date%' = (Get-Date -Format 'yyyy-MM-dd')
+ '%dayofweek%' = (Get-Date).DayOfWeek
+ '%day%' = (Get-Date).Day
+ '%hour%' = (Get-Date).Hour
+ '%minute%' = (Get-Date).Minute
+ '%username%' = $env:USERNAME
+ '%userdomain%' = $env:USERDOMAIN
'%computername%' = $env:COMPUTERNAME
- '%processid%' = $PID
- '%logname%' = $logname
+ '%processid%' = $PID
+ '%logname%' = $logname
}
$hash.$Match
@@ -64,84 +64,93 @@
$MessageItem
)
- #region Type-Based Output
- switch ($FileType) {
- #region Csv
- "Csv"
- {
- if (-not $CsvDelimiter) {
- $CsvDelimiter = ','
- }
+ process {
+ #region Type-Based Output
+ switch ($FileType) {
+ #region Csv
+ "Csv" {
+ if (-not $CsvDelimiter) {
+ $CsvDelimiter = ','
+ }
- if ($script:firstEntry) {
- if ($script:csvConverter) {
- $null = $script:csvConverter.End()
- $script:csvConverter = $null
+ if ($script:firstEntry) {
+ if ($script:csvConverter) {
+ $null = $script:csvConverter.End()
+ $script:csvConverter = $null
+ }
}
+ if (-not $script:csvConverter) {
+ $script:csvConverter = { ConvertTo-Csv -NoTypeInformation -Delimiter $CsvDelimiter }.GetSteppablePipeline()
+ $script:csvConverter.Begin($true)
+ }
+ $converted = $script:csvConverter.Process($Message)
+ if ($script:firstEntry) {
+ if ($IncludeHeader -and (Test-EmptyFile -Path $script:currentPath)) { $script:writer.WriteLine($converted[0]) }
+ $script:writer.WriteLine($converted[1])
+ }
+ else { $script:writer.WriteLine($converted[0]) }
}
- if (-not $script:csvConverter) {
- $script:csvConverter = { ConvertTo-Csv -NoTypeInformation -Delimiter $CsvDelimiter }.GetSteppablePipeline()
- $script:csvConverter.Begin($true)
- }
- $converted = $script:csvConverter.Process($Message)
- if ($script:firstEntry) {
- if ($IncludeHeader -and (Test-EmptyFile -Path $script:currentPath)) { $script:writer.WriteLine($converted[0]) }
- $script:writer.WriteLine($converted[1])
- }
- else { $script:writer.WriteLine($converted[0]) }
- }
- #endregion Csv
- #region Json
- "Json"
- {
- if (-not $script:JsonSettings.JsonString) { $data = $Message | ConvertTo-Json -Compress:$script:JsonSettings.JsonCompress }
- else { $data = $Message | ConvertFrom-Enumeration | ConvertTo-Json -Compress:$script:JsonSettings.JsonCompress }
+ #endregion Csv
+ #region Json
+ "Json" {
+ if (-not $script:JsonSettings.JsonString) { $data = $Message | ConvertTo-Json -Compress:$script:JsonSettings.JsonCompress }
+ else { $data = $Message | ConvertFrom-Enumeration | ConvertTo-Json -Compress:$script:JsonSettings.JsonCompress }
- if (-not $script:JsonSettings.JsonNoComma) {
- $script:writer.WriteLine(",")
- $script:writer.Write($data)
+ if ($script:JsonSettings.JsonNoComma) {
+ $script:writer.WriteLine($data)
+ }
+ else {
+ if (-not $script:firstEntry -or (-not $script:JsonSettings.JsonNoEmptyFirstLine)) {
+ $script:writer.WriteLine(",")
+ }
+ $script:writer.Write($data)
+ }
}
- else {
- $script:writer.WriteLine($data)
+ #endregion Json
+ #region XML
+ "XML" {
+ [xml]$xml = $message | ConvertTo-Xml -NoTypeInformation
+ $script:writer.WriteLine($xml.Objects.InnerXml)
}
- }
- #endregion Json
- #region XML
- "XML"
- {
- [xml]$xml = $message | ConvertTo-Xml -NoTypeInformation
- $script:writer.WriteLine($xml.Objects.InnerXml)
- }
- #endregion XML
- #region Html
- "Html"
- {
- [xml]$xml = $message | ConvertTo-Html -Fragment
+ #endregion XML
+ #region Html
+ "Html" {
+ [xml]$xml = $message | ConvertTo-Html -Fragment
- if ($script:firstEntry -and $IncludeHeader) {
- $script:writer.WriteLine($xml.table.tr[0].OuterXml)
+ if ($script:firstEntry -and $IncludeHeader) {
+ $script:writer.WriteLine($xml.table.tr[0].OuterXml)
+ }
+ $script:writer.WriteLine($xml.table.tr[1].OuterXml)
}
- $script:writer.WriteLine($xml.table.tr[1].OuterXml)
- }
- #endregion Html
- #region CMTrace
- "CMTrace"
- {
- $cType = 1
- if ($MessageItem.Level -eq 'Warning') { $cType = 2 }
- if ($MessageItem.ErrorRecord) { $cType = 3 }
- $fileEntry = ''
- if ($MessageItem.File) { $fileEntry = Split-Path -Path $MessageItem.File -Leaf }
+ #endregion Html
+ #region CMTrace
+ "CMTrace" {
+ $cType = 1
+ if ($MessageItem.Level -eq 'Warning') { $cType = 2 }
+ if ($MessageItem.ErrorRecord) { $cType = 3 }
+ $fileEntry = ''
+ if ($MessageItem.File) { $fileEntry = Split-Path -Path $MessageItem.File -Leaf }
+
+ $component = "{0}:{1} > {2}" -f $fileEntry, $MessageItem.Line, $MessageItem.FunctionName
+ if ($script:cmTraceSettings.CMTraceOverrideComponent -and $MessageItem.Data.Component) {
+ $component = $MessageItem.Data.Component
+ }
- $format = '
[Cmdlet(VerbsData.ConvertTo, "PSFHashtable")]
[OutputType(new Type[] { typeof(Hashtable) })]
- public class ConvertToPSFHashtableCommand : PSCmdlet
+ public class ConvertToPSFHashtableCommand : PSFCmdlet
{
#region Parameters
///
@@ -47,6 +48,12 @@ public class ConvertToPSFHashtableCommand : PSCmdlet
[Parameter()]
public SwitchParameter Inherit;
+ ///
+ /// Inherit from all parameters of the calling command, including all non-bound parameters.
+ ///
+ [Parameter()]
+ public SwitchParameter InheritParameters;
+
///
/// Remap individual keys in the hashtable provided.
/// Effectively renames entries in the hashtable.
@@ -82,6 +89,7 @@ public class ConvertToPSFHashtableCommand : PSCmdlet
StringComparer _Comparison = StringComparer.InvariantCultureIgnoreCase;
List _ToInclude = new List();
+ List _ReferenceParameters = new List();
#region Cmdlet Methods
///
@@ -107,10 +115,12 @@ protected override void BeginProcessing()
CommandInfo info = null;
using (PowerShell ps = PowerShell.Create(RunspaceMode.CurrentRunspace))
{
- ps.AddCommand("Get-Command")
+ ps.AddCommand(InvokeCommand.GetCmdlet("Get-Command"))
.AddParameter("Name", ReferenceCommand)
.AddParameter("ErrorAction", ActionPreference.SilentlyContinue);
- info = ps.Invoke()[0]?.BaseObject as CommandInfo;
+ var result = ps.Invoke();
+ if (result != null && result.Count > 0)
+ info = result[0].BaseObject as CommandInfo;
}
PSFCoreHost.WriteDebug("ConvertTo-PSFHashTable: ReferenceCommand", info);
@@ -118,7 +128,10 @@ protected override void BeginProcessing()
if (info == null)
throw new CommandNotFoundException($"Unable to find command: {ReferenceCommand}");
if (String.IsNullOrEmpty(ReferenceParameterSetName))
+ {
_ToInclude.AddRange(info.Parameters.Keys.ToArray());
+ _ReferenceParameters.AddRange(info.Parameters.Keys.ToArray());
+ }
else
{
var parameterSets = info.ParameterSets.Where(o => String.Equals(o.Name, ReferenceParameterSetName, StringComparison.InvariantCultureIgnoreCase));
@@ -126,6 +139,7 @@ protected override void BeginProcessing()
throw new ArgumentException($"Parameterset {ReferenceParameterSetName} not found on command {info.Name}!");
PSFCoreHost.WriteDebug("ConvertTo-PSFHashTable: ReferenceCommand / ParameterSet", parameterSets);
_ToInclude.AddRange(parameterSets.First().Parameters.Select(o => o.Name).ToArray());
+ _ReferenceParameters.AddRange(parameterSets.First().Parameters.Select(o => o.Name).ToArray());
}
}
@@ -156,8 +170,16 @@ protected override void ProcessRecord()
}
else
+ {
foreach (string name in inputItem.Properties.Select(o => o.Name))
- result[name] = inputItem.Properties[name].Value;
+ {
+ try { result[name] = inputItem.Properties[name].Value; }
+ catch (Exception e){
+ PSFCoreHost.WriteDebug($"ConvertTo-PSFHashTable: Failed to access property {name}: {e.Message}", e);
+ result[name] = null;
+ }
+ }
+ }
if (Exclude.Length > 0)
foreach (string key in Exclude.Where(o => result.ContainsKey(o)))
@@ -170,22 +192,37 @@ protected override void ProcessRecord()
foreach (string key in keys.Where(o => !_ToInclude.Contains(o.ToString(), _Comparison) && result.ContainsKey(o)))
result.Remove(key);
if (Inherit.ToBool())
- foreach (string name in _ToInclude.Where(o => !result.ContainsKey(o)).Where(o => GetVariableValue(o) != null))
+ foreach (string name in _ToInclude.Where(o => !result.ContainsKey(o) && GetVariableValue(o) != null && (Exclude == null || !Exclude.Contains(o, StringComparer.OrdinalIgnoreCase))))
result[name] = GetVariableValue(name);
if (IncludeEmpty.ToBool())
foreach (string name in _ToInclude.Where(o => !result.ContainsKey(o)))
result[name] = null;
}
+ if (InheritParameters.ToBool())
+ {
+ CallStackFrame caller = GetCaller();
+ Dictionary parameters = GetCommandParameters(caller.InvocationInfo.MyCommand);
+ foreach (ParameterAst parameter in parameters.Values)
+ {
+ if (result.ContainsKey(parameter.Name.VariablePath.UserPath) || (Exclude != null && Exclude.Contains(parameter.Name.VariablePath.UserPath, StringComparer.OrdinalIgnoreCase)))
+ continue;
+ if (!IsValidParameter(parameter, IncludeEmpty.ToBool(), caller.InvocationInfo.BoundParameters))
+ continue;
+ if (_ReferenceParameters.Count > 0 && !_ReferenceParameters.Contains(parameter.Name.VariablePath.UserPath, StringComparer.OrdinalIgnoreCase))
+ continue;
+ result[parameter.Name.VariablePath.UserPath] = GetVariableValue(parameter.Name.VariablePath.UserPath);
+ }
+ }
if (Remap != null)
{
foreach (string key in Remap.Keys)
{
- if (result.ContainsKey(key))
- {
- object value = result[key];
- result.Remove(key);
- result[Remap[key]] = value;
- }
+ if (!result.ContainsKey(key))
+ continue;
+
+ object value = result[key];
+ result.Remove(key);
+ result[Remap[key]] = value;
}
}
@@ -196,5 +233,38 @@ protected override void ProcessRecord()
}
}
#endregion Cmdlet Methods
+
+ #region Internal Helpers
+ private bool IsValidParameter(ParameterAst Parameter, bool IncludeEmpty, Dictionary BoundParameters)
+ {
+ if (Parameter.DefaultValue != null || IncludeEmpty)
+ return true;
+
+ if (BoundParameters.ContainsKey(Parameter.Name.VariablePath.UserPath))
+ return true;
+
+ return false;
+ }
+
+ private Dictionary GetCommandParameters(CommandInfo Command)
+ {
+ Dictionary result = new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ if (Command as FunctionInfo == null && Command as ScriptInfo == null && Command as ExternalScriptInfo == null)
+ return result;
+
+ if (Command as FunctionInfo != null)
+ foreach (ParameterAst parameter in ((FunctionDefinitionAst)((FunctionInfo)Command).ScriptBlock.Ast).Body.ParamBlock.Parameters)
+ result[parameter.Name.VariablePath.UserPath] = parameter;
+ else if (Command as ExternalScriptInfo != null)
+ foreach (ParameterAst parameter in ((ScriptBlockAst)((ExternalScriptInfo)Command).ScriptBlock.Ast).ParamBlock.Parameters)
+ result[parameter.Name.VariablePath.UserPath] = parameter;
+ else
+ foreach (ParameterAst parameter in ((ScriptBlockAst)((ScriptInfo)Command).ScriptBlock.Ast).ParamBlock.Parameters)
+ result[parameter.Name.VariablePath.UserPath] = parameter;
+
+ return result;
+ }
+ #endregion Internal Helpers
}
}
\ No newline at end of file
diff --git a/library/PSFramework/Commands/InvokePSFProtectedCommand.cs b/library/PSFramework/Commands/InvokePSFProtectedCommand.cs
index 8c2a464e..24e47a75 100644
--- a/library/PSFramework/Commands/InvokePSFProtectedCommand.cs
+++ b/library/PSFramework/Commands/InvokePSFProtectedCommand.cs
@@ -2,6 +2,7 @@
using PSFramework.Utility;
using System;
using System.Collections;
+using System.Diagnostics;
using System.Linq;
using System.Management.Automation;
using System.Text;
@@ -122,6 +123,12 @@ public class InvokePSFProtectedCommand : PSFCmdlet
///
[Parameter()]
public Message.MessageLevel Level = Message.MessageLevel.SomewhatVerbose;
+
+ ///
+ /// Make the final error generated a nonterminating error
+ ///
+ [Parameter()]
+ public SwitchParameter NonTerminating;
#endregion Parameters
#region Private Fields
@@ -289,6 +296,29 @@ protected override void ProcessRecord()
private void Terminate(Exception error)
{
ErrorEventAction(error);
+ if (NonTerminating.ToBool())
+ {
+ WriteMessage(_ErrorMessage, Message.MessageLevel.Error, _Caller.CallerFunction, _Caller.CallerModule, _Caller.CallerFile, _Caller.CallerLine, Tag, Target, null, error);
+ if (EnableException)
+ {
+ if (error as RuntimeException != null)
+ PSCmdlet.WriteError(((RuntimeException)error).ErrorRecord);
+ else
+ PSCmdlet.WriteError(
+ new ErrorRecord(
+ error,
+ "ItFailed",
+ ErrorCategory.NotSpecified,
+ Target
+ )
+ );
+ }
+
+ if (Continue)
+ DoContinue(ContinueLabel);
+ return;
+ }
+
ScriptBlock errorBlock = ScriptBlock.Create(_ErrorScript);
object[] arguments = new object[] { _ErrorMessage, error, Target, Continue, ContinueLabel, _Caller.CallerFunction, _Caller.CallerModule, _Caller.CallerFile, _Caller.CallerLine, PSCmdlet, EnableException };
PSCmdlet.InvokeCommand.InvokeScript(false, errorBlock, null, arguments);
diff --git a/library/PSFramework/Commands/PSFCmdlet.cs b/library/PSFramework/Commands/PSFCmdlet.cs
index 5f3b2763..1930d058 100644
--- a/library/PSFramework/Commands/PSFCmdlet.cs
+++ b/library/PSFramework/Commands/PSFCmdlet.cs
@@ -7,7 +7,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
-using System.Reflection.Emit;
+using System.Reflection;
namespace PSFramework.Commands
{
@@ -109,7 +109,8 @@ public bool TestFeature(string Name)
/// Tags to attach to this message
/// A target object to specify
/// Add additional metadata to the message written
- public void WriteMessage(string Message, MessageLevel Level, string FunctionName, string ModuleName, string File, int Line, string[] Tag, object Target, Hashtable Data = null)
+ /// Exception to include in the message
+ public void WriteMessage(string Message, MessageLevel Level, string FunctionName, string ModuleName, string File, int Line, string[] Tag, object Target, Hashtable Data = null, Exception Error = null)
{
using (PowerShell ps = PowerShell.Create(RunspaceMode.CurrentRunspace))
{
@@ -122,8 +123,11 @@ public void WriteMessage(string Message, MessageLevel Level, string FunctionName
.AddParameter("File", File)
.AddParameter("Line", Line)
.AddParameter("Tag", Tag)
- .AddParameter("Target", Target)
- .AddParameter("Data", Data);
+ .AddParameter("Target", Target);
+ if (Data != null)
+ ps.AddParameter("Data", Data);
+ if (Error != null)
+ ps.AddParameter("Exception", Error);
ps.Invoke();
}
}
@@ -141,7 +145,8 @@ public void WriteMessage(string Message, MessageLevel Level, string FunctionName
/// Tags to attach to this message
/// A target object to specify
/// Add additional metadata to the message written
- public void WriteLocalizedMessage(string String, object[] StringValues, MessageLevel Level, string FunctionName, string ModuleName, string File, int Line, string[] Tag, object Target, Hashtable Data = null)
+ /// Exception to include in the message
+ public void WriteLocalizedMessage(string String, object[] StringValues, MessageLevel Level, string FunctionName, string ModuleName, string File, int Line, string[] Tag, object Target, Hashtable Data = null, Exception Error = null)
{
using (PowerShell ps = PowerShell.Create(RunspaceMode.CurrentRunspace))
{
@@ -155,8 +160,11 @@ public void WriteLocalizedMessage(string String, object[] StringValues, MessageL
.AddParameter("File", File)
.AddParameter("Line", Line)
.AddParameter("Tag", Tag)
- .AddParameter("Target", Target)
- .AddParameter("Data", Data);
+ .AddParameter("Target", Target);
+ if (Data != null)
+ ps.AddParameter("Data", Data);
+ if (Error != null)
+ ps.AddParameter("Exception", Error);
ps.Invoke();
}
}
@@ -243,6 +251,21 @@ public void StopLocalizedCommand(string String, object[] StringValues, Exception
///
public bool IsStopping;
+ ///
+ /// Throw a continue exception, equivalent to calling continue in script
+ ///
+ ///
+ public void DoContinue(string Label = "")
+ {
+ ConstructorInfo[] constructors = typeof(ContinueException).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);
+ ConstructorInfo constructor = constructors.Where(o => o.GetParameters().Count() == 0).First();
+ if (String.IsNullOrEmpty(Label))
+ throw (ContinueException)constructor.Invoke(new object[0]);
+
+ constructor = constructors.Where(o => o.GetParameters().Count() == 1 && o.GetParameters()[0].Name == "label").First();
+ throw (ContinueException)constructor.Invoke(new object[] { Label });
+ }
+
///
/// Invokes all applicable callback scripts.
///
diff --git a/library/PSFramework/Commands/SelectPSFObjectCommand.cs b/library/PSFramework/Commands/SelectPSFObjectCommand.cs
index 2fc3c9ab..445ed655 100644
--- a/library/PSFramework/Commands/SelectPSFObjectCommand.cs
+++ b/library/PSFramework/Commands/SelectPSFObjectCommand.cs
@@ -207,36 +207,11 @@ protected override void ProcessRecord()
_Pipeline.Process(InputObject);
else
{
- PSObject item = PSObject.AsPSObject(_Pipeline.Process(InputObject).GetValue(0));
-
- if (KeepInputObject.ToBool())
- {
- PSObject tempItem = item;
- item = InputObject;
- foreach (PSPropertyInfo info in tempItem.Properties.Where(o => !item.Properties.Select(n => n.Name).Contains(o.Name)))
- item.Properties.Add(info);
- }
-
- if (Alias != null)
- foreach (SelectAliasParameter alias in Alias)
- foreach (PSAliasProperty aliasItem in alias.Aliases)
- item.Members.Add(aliasItem);
- if (ScriptMethod != null)
- foreach (SelectScriptMethodParameter method in ScriptMethod)
- foreach (PSScriptMethod methodItem in method.Methods)
- item.Members.Add(methodItem);
- if (ScriptProperty != null)
- foreach (SelectScriptPropertyParameter property in ScriptProperty)
- foreach (PSScriptProperty propertyItem in property.Value)
- item.Members.Add(propertyItem);
-
- if (ShowProperty.Length > 0)
- item.Members.Add(new PSMemberSet("PSStandardMembers", _DisplayPropertySet));
- else if (ShowExcludeProperty.Length > 0)
- item.Members.Add(new PSMemberSet("PSStandardMembers", new PSMemberInfo[] { new PSPropertySet("DefaultDisplayPropertySet", item.Properties.Select(o => o.Name).Where(o => !ShowExcludeProperty.Contains(o))) }));
- if (!String.IsNullOrEmpty(TypeName))
- item.TypeNames.Insert(0, TypeName);
- WriteObject(item);
+ Array items = _Pipeline.Process(InputObject);
+ if (items.Length == 0)
+ return;
+ foreach (object item in items)
+ WriteObject(ConvertResult(item));
}
}
@@ -245,8 +220,55 @@ protected override void ProcessRecord()
///
protected override void EndProcessing()
{
- _Pipeline.End();
+ Array items = _Pipeline.End();
+
+ if (items.Length == 0)
+ return;
+ foreach (object item in items)
+ WriteObject(ConvertResult(item));
}
#endregion Command Implementation
+
+ #region Internal Helpers
+ ///
+ /// Converts a select-object result based on extra elements to add
+ ///
+ /// The item to convert / extend
+ /// A fully converted object
+ private PSObject ConvertResult(object Item)
+ {
+ PSObject item = PSObject.AsPSObject(Item);
+
+ if (KeepInputObject.ToBool())
+ {
+ PSObject tempItem = item;
+ item = InputObject;
+ foreach (PSPropertyInfo info in tempItem.Properties.Where(o => !item.Properties.Select(n => n.Name).Contains(o.Name)))
+ item.Properties.Add(info);
+ }
+
+ if (Alias != null)
+ foreach (SelectAliasParameter alias in Alias)
+ foreach (PSAliasProperty aliasItem in alias.Aliases)
+ item.Members.Add(aliasItem);
+ if (ScriptMethod != null)
+ foreach (SelectScriptMethodParameter method in ScriptMethod)
+ foreach (PSScriptMethod methodItem in method.Methods)
+ item.Members.Add(methodItem);
+ if (ScriptProperty != null)
+ foreach (SelectScriptPropertyParameter property in ScriptProperty)
+ foreach (PSScriptProperty propertyItem in property.Value)
+ item.Members.Add(propertyItem);
+
+ if (ShowProperty.Length > 0)
+ item.Members.Add(new PSMemberSet("PSStandardMembers", _DisplayPropertySet));
+ else if (ShowExcludeProperty.Length > 0)
+ item.Members.Add(new PSMemberSet("PSStandardMembers", new PSMemberInfo[] { new PSPropertySet("DefaultDisplayPropertySet", item.Properties.Select(o => o.Name).Where(o => !ShowExcludeProperty.Contains(o))) }));
+ if (!String.IsNullOrEmpty(TypeName))
+ item.TypeNames.Insert(0, TypeName);
+
+ return item;
+ }
+ #endregion Internal Helpers
}
}
diff --git a/library/PSFramework/Commands/UpdatePSFTeppCompletionCommand.cs b/library/PSFramework/Commands/UpdatePSFTeppCompletionCommand.cs
new file mode 100644
index 00000000..54d17d3e
--- /dev/null
+++ b/library/PSFramework/Commands/UpdatePSFTeppCompletionCommand.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+using PSFramework.Meta;
+using PSFramework.TabExpansion;
+
+namespace PSFramework.Commands
+{
+ ///
+ /// Imports provided values to parameters configured for an auto-training completer.
+ ///
+ [Cmdlet(VerbsData.Update, "PSFTeppCompletion")]
+ public class UpdatePSFTeppCompletionCommand : PSFCmdlet
+ {
+ ///
+ /// Execute the main (and only) step: Copying over values provided to the calling command as parameter into the completion cache.
+ /// Only applies to parameters configured for auto-training tab completers.
+ ///
+ protected override void ProcessRecord()
+ {
+ CallStackFrame caller = GetCaller();
+ foreach (var pair in caller.InvocationInfo.BoundParameters)
+ {
+ // If the parameter does not exist, something weird is happening and we want no part in it
+ if (!caller.InvocationInfo.MyCommand.Parameters.ContainsKey(pair.Key))
+ continue;
+
+ PsfArgumentCompleterAttribute attribute = (PsfArgumentCompleterAttribute)caller.InvocationInfo.MyCommand.Parameters[pair.Key].Attributes.Where(o => o.TypeId.ToString() == "PSFramework.TabExpansion.PsfArgumentCompleterAttribute").FirstOrDefault();
+ // Without our Argument Completer attribute, there is nothing to cache
+ if (attribute == null || attribute.CompletionName == "")
+ continue;
+
+ // Do not cache if completer does not exist or is not configured to autocache
+ if (!TabExpansionHost.Scripts.ContainsKey(attribute.CompletionName) || !TabExpansionHost.Scripts[attribute.CompletionName].AutoTraining)
+ continue;
+
+ // Do not cache empty values
+ if (pair.Value == null)
+ continue;
+
+ string converted = LanguagePrimitives.ConvertTo(pair.Value);
+
+ // Do not cache values that resolve to empty-string or their typename
+ if (String.IsNullOrEmpty(converted) || converted == pair.Value.GetType().FullName)
+ continue;
+
+ TabExpansionHost.Scripts[attribute.CompletionName].AddTraining(converted);
+ }
+ }
+ }
+}
diff --git a/library/PSFramework/Commands/WritePSFMessageCommand.cs b/library/PSFramework/Commands/WritePSFMessageCommand.cs
index dae1752b..0a577eca 100644
--- a/library/PSFramework/Commands/WritePSFMessageCommand.cs
+++ b/library/PSFramework/Commands/WritePSFMessageCommand.cs
@@ -1,4 +1,5 @@
using PSFramework.Message;
+using PSFramework.Utility;
using System;
using System.Collections;
using System.Collections.Generic;
@@ -147,8 +148,16 @@ public class WritePSFMessageCommand : PSCmdlet
/// This is less user friendly, but allows catching exceptions in calling scripts.
///
[Parameter()]
+ [TypeTransformation(typeof(Boolean))]
public bool EnableException;
+ ///
+ /// Create a new error record, using the current message, then write it.
+ /// This parameter is only respected if EnableException is set to true.
+ ///
+ [Parameter()]
+ public SwitchParameter NewErrorRecord;
+
///
/// Enables breakpoints on the current message. By default, setting '-Debug' will NOT cause an interrupt on the current position.
///
@@ -525,7 +534,7 @@ whether this function was called from Stop-PSFFunction.
InvokeCommand.InvokeScript(@"$VerbosePreference = 'Continue'");
//SessionState.PSVariable.Set("VerbosePreference", ActionPreference.Continue);
- WriteVerbose(_MessageStreams);
+ WriteVerbose(_MessageStreams);
channels = channels | LogEntryType.Verbose;
}
@@ -567,10 +576,22 @@ whether this function was called from Stop-PSFFunction.
}
#region Error handling p2
- if (ErrorRecord != null)
- if (!_fromStopFunction && EnableException)
+ if (!_fromStopFunction && EnableException)
+ {
+ if (NewErrorRecord)
+ {
+ ErrorRecord record = new ErrorRecord(
+ new Exception(_errorQualifiedMessage),
+ "ErrorMessage",
+ ErrorCategory.NotSpecified,
+ Target
+ );
+ PSCmdlet.WriteError(record);
+ }
+ else if (ErrorRecord != null)
foreach (ErrorRecord record in ErrorRecord)
PSCmdlet.WriteError(record);
+ }
#endregion Error handling p2
}
#endregion Cmdlet Implementation
@@ -782,7 +803,10 @@ private string GetMessageColor()
levelDisplay = $"[{Level}]";
string timeStamp = "";
if (MessageHost.EnableMessageTimestamp)
- timeStamp = $"[{_timestamp.ToString("HH:mm:ss")}]";
+ timeStamp = $"[{_timestamp.ToString(MessageHost.TimeFormat)}]";
+ string target = "";
+ if (MessageHost.EnableMessageTarget && Target != null)
+ target = $"[{Target.ToString()}]";
string breadCrumbs = "";
if (MessageHost.EnableMessageBreadcrumbs)
breadCrumbs = _BreadCrumbsStringColored;
@@ -790,7 +814,7 @@ private string GetMessageColor()
if (MessageHost.EnableMessageDisplayCommand && !MessageHost.EnableMessageBreadcrumbs)
functionText = $"[{FunctionName}]";
- string header = String.Join("", timeStamp, functionText, levelDisplay, breadCrumbs);
+ string header = String.Join("", timeStamp, functionText, levelDisplay, target, breadCrumbs);
if (MessageHost.EnableMessageBreadcrumbs)
_messageColor = String.Join("", header, _errorQualifiedMessage);
diff --git a/library/PSFramework/Configuration/ConfigurationHost.cs b/library/PSFramework/Configuration/ConfigurationHost.cs
index fd36354a..10c033cd 100644
--- a/library/PSFramework/Configuration/ConfigurationHost.cs
+++ b/library/PSFramework/Configuration/ConfigurationHost.cs
@@ -139,6 +139,8 @@ public static object ConvertFromPersistedValue(string PersistedValue, Configurat
case ConfigurationValueType.ConsoleColor:
return Enum.Parse(typeof(ConsoleColor), PersistedValue);
case ConfigurationValueType.Hashtable:
+ if (PersistedValue == "")
+ return new Hashtable(StringComparer.OrdinalIgnoreCase);
string[] hashItems = PersistedValue.Split(new string[1] { "þHþ" }, StringSplitOptions.None);
Hashtable tempTable = new Hashtable();
foreach (string tempValue in hashItems)
diff --git a/library/PSFramework/Data/Converters/ArrayConverter.cs b/library/PSFramework/Data/Converters/ArrayConverter.cs
new file mode 100644
index 00000000..03932e0a
--- /dev/null
+++ b/library/PSFramework/Data/Converters/ArrayConverter.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Data.Converters
+{
+ ///
+ /// Converts data structures into PSD1 Strings
+ ///
+ public class ArrayConverter : IPsd1Converter
+ {
+ ///
+ /// Converts to psd1 string
+ ///
+ /// The object to convert
+ /// The indentation level
+ /// The conversion runtime object, including its settings.
+ /// The parent objects. Used in complex data types to prevent infinite recursion.
+ /// The converted PSD1 representation of the value provided
+ public string Convert(object Value, object[] Parents, int Depth, Psd1Converter Converter)
+ {
+ StringBuilder sb = new StringBuilder();
+ string indent = new string(' ', Depth * 4);
+ string newIndent = new string(' ', (Depth + 1) * 4);
+ sb.AppendLine("@(");
+
+ foreach (object item in (IEnumerable)Value)
+ sb.AppendLine($"{newIndent}{DataHost.Convert(item, Parents, Depth + 1, Converter)}");
+
+ sb.Append($"{indent})");
+ return sb.ToString();
+ }
+ }
+}
diff --git a/library/PSFramework/Data/Converters/AssemblyConverter.cs b/library/PSFramework/Data/Converters/AssemblyConverter.cs
new file mode 100644
index 00000000..aa4f853f
--- /dev/null
+++ b/library/PSFramework/Data/Converters/AssemblyConverter.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Data.Converters
+{
+ ///
+ /// Converts data structures into PSD1 Strings
+ ///
+ public class AssemblyConverter : IPsd1Converter
+ {
+ ///
+ /// Converts to psd1 string
+ ///
+ /// The object to convert
+ /// The indentation level
+ /// The conversion runtime object, including its settings.
+ /// The parent objects. Used in complex data types to prevent infinite recursion.
+ /// The converted PSD1 representation of the value provided
+ public string Convert(object Value, object[] Parents, int Depth, Psd1Converter Converter)
+ {
+ return $"'{((Assembly)Value).FullName}'";
+ }
+ }
+}
diff --git a/library/PSFramework/Data/Converters/BoolConverter.cs b/library/PSFramework/Data/Converters/BoolConverter.cs
new file mode 100644
index 00000000..d94c0fc7
--- /dev/null
+++ b/library/PSFramework/Data/Converters/BoolConverter.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Data.Converters
+{
+ ///
+ /// Converts data structures into PSD1 Strings
+ ///
+ public class BoolConverter : IPsd1Converter
+ {
+ ///
+ /// Converts to psd1 string
+ ///
+ /// The object to convert
+ /// The indentation level
+ /// The conversion runtime object, including its settings.
+ /// The parent objects. Used in complex data types to prevent infinite recursion.
+ /// The converted PSD1 representation of the value provided
+ public string Convert(object Value, object[] Parents, int Depth, Psd1Converter Converter)
+ {
+ return $"${Value}";
+ }
+ }
+}
diff --git a/library/PSFramework/Data/Converters/CustomConverter.cs b/library/PSFramework/Data/Converters/CustomConverter.cs
new file mode 100644
index 00000000..93841d1d
--- /dev/null
+++ b/library/PSFramework/Data/Converters/CustomConverter.cs
@@ -0,0 +1,100 @@
+using PSFramework.Utility;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Management.Automation.Language;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Data.Converters
+{
+ ///
+ /// Converts data structures into PSD1 Strings
+ ///
+ public class CustomConverter : IPsd1Converter
+ {
+ ///
+ /// The type the custom converter is assigned to
+ ///
+ public Type AssignedType;
+
+ ///
+ /// The code calculating the new PSD1-String
+ ///
+ public PsfScriptBlock Code;
+
+ ///
+ /// Any additional properties to assign to this.
+ /// These properties are made available to the code via the $this variable
+ ///
+ public Hashtable Properties = new Hashtable(StringComparer.OrdinalIgnoreCase);
+
+ ///
+ /// The result to return as a default.
+ /// This is mostly used in combination with "OnErrorDefault"
+ ///
+ public string DefaultResult = "$null";
+
+ ///
+ /// Whether in case of an error it should just return a default string, rather than failing the entire conversion.
+ ///
+ public bool OnErrorDefault = false;
+
+ ///
+ /// Converts to psd1 string
+ ///
+ /// The object to convert
+ /// The indentation level
+ /// The conversion runtime object, including its settings.
+ /// The parent objects. Used in complex data types to prevent infinite recursion.
+ /// The converted PSD1 representation of the value provided
+ public string Convert(object Value, object[] Parents, int Depth, Psd1Converter Converter)
+ {
+ Hashtable data = new Hashtable(StringComparer.OrdinalIgnoreCase);
+ data["Converter"] = Converter;
+ data["Properties"] = Properties;
+ data["Depth"] = Depth;
+ data["Parents"] = Parents;
+ data["Value"] = Value;
+
+ System.Collections.ObjectModel.Collection results = null;
+ try { results = Code.InvokeEx(false, Value, Value, data, false, false, Depth, Converter); }
+ catch (Exception e) {
+ Converter.WriteVerbose($"Custom Converter Error. Assigned Type: {AssignedType.Name} | Data: {Value} | Error: {e.Message}");
+ if (OnErrorDefault)
+ return DefaultResult;
+ throw e;
+ }
+
+ if (results.Count < 1)
+ {
+ Converter.WriteVerbose($"Custom Converter Failed. Assigned Type: {AssignedType.Name} | Data: {Value} | No results returned");
+ if (OnErrorDefault)
+ return DefaultResult;
+ throw new InvalidOperationException("Custom converter returned no content!");
+ }
+ if (String.IsNullOrEmpty(LanguagePrimitives.ConvertTo(results[0].BaseObject)))
+ {
+ Converter.WriteVerbose($"Custom Converter Failed. Assigned Type: {AssignedType.Name} | Data: {Value} | Results is null or empty");
+ if (OnErrorDefault)
+ return DefaultResult;
+ throw new InvalidOperationException("Custom converter returned null or an empty string!");
+ }
+
+ return LanguagePrimitives.ConvertTo(results[0].BaseObject);
+ }
+
+ ///
+ /// Creates a new custom converter
+ ///
+ /// The type that should be converted
+ /// The code doing the conversion
+ public CustomConverter(Type AssignedType, PsfScriptBlock Code)
+ {
+ this.AssignedType = AssignedType;
+ this.Code = Code;
+ }
+ }
+}
diff --git a/library/PSFramework/Data/Converters/DBNullConverter.cs b/library/PSFramework/Data/Converters/DBNullConverter.cs
new file mode 100644
index 00000000..f42654d3
--- /dev/null
+++ b/library/PSFramework/Data/Converters/DBNullConverter.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Data.Converters
+{
+ ///
+ /// Converts data structures into PSD1 Strings
+ ///
+ public class DBNullConverter : IPsd1Converter
+ {
+ ///
+ /// Converts to psd1 string
+ ///
+ /// The object to convert
+ /// The indentation level
+ /// The conversion runtime object, including its settings.
+ /// The parent objects. Used in complex data types to prevent infinite recursion.
+ /// The converted PSD1 representation of the value provided
+ public string Convert(object Value, object[] Parents, int Depth, Psd1Converter Converter)
+ {
+ return "$null";
+ }
+ }
+}
diff --git a/library/PSFramework/Data/Converters/DateTimeConverter.cs b/library/PSFramework/Data/Converters/DateTimeConverter.cs
new file mode 100644
index 00000000..b81262e0
--- /dev/null
+++ b/library/PSFramework/Data/Converters/DateTimeConverter.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Data.Converters
+{
+ ///
+ /// Converts data structures into PSD1 Strings
+ ///
+ public class DateTimeConverter : IPsd1Converter
+ {
+ ///
+ /// Converts to psd1 string
+ ///
+ /// The object to convert
+ /// The indentation level
+ /// The conversion runtime object, including its settings.
+ /// The parent objects. Used in complex data types to prevent infinite recursion.
+ /// The converted PSD1 representation of the value provided
+ public string Convert(object Value, object[] Parents, int Depth, Psd1Converter Converter)
+ {
+ return String.Format("'{0:yyyy-MM-dd HH:mm:ss.fffff zzz}'", Value);
+ }
+ }
+}
diff --git a/library/PSFramework/Data/Converters/DoubleConverter.cs b/library/PSFramework/Data/Converters/DoubleConverter.cs
new file mode 100644
index 00000000..e203b3cb
--- /dev/null
+++ b/library/PSFramework/Data/Converters/DoubleConverter.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Data.Converters
+{
+ ///
+ /// Converts data structures into PSD1 Strings
+ ///
+ public class DoubleConverter : IPsd1Converter
+ {
+ ///
+ /// Converts to psd1 string
+ ///
+ /// The object to convert
+ /// The indentation level
+ /// The conversion runtime object, including its settings.
+ /// The parent objects. Used in complex data types to prevent infinite recursion.
+ /// The converted PSD1 representation of the value provided
+ public string Convert(object Value, object[] Parents, int Depth, Psd1Converter Converter)
+ {
+ return $"{Value}";
+ }
+ }
+}
diff --git a/library/PSFramework/Data/Converters/FileSystemInfoConverter.cs b/library/PSFramework/Data/Converters/FileSystemInfoConverter.cs
new file mode 100644
index 00000000..67fb674d
--- /dev/null
+++ b/library/PSFramework/Data/Converters/FileSystemInfoConverter.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Management.Automation.Language;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Data.Converters
+{
+ ///
+ /// Converts data structures into PSD1 Strings
+ ///
+ public class FileSystemInfoConverter : IPsd1Converter
+ {
+ ///
+ /// Converts to psd1 string
+ ///
+ /// The object to convert
+ /// The indentation level
+ /// The conversion runtime object, including its settings.
+ /// The parent objects. Used in complex data types to prevent infinite recursion.
+ /// The converted PSD1 representation of the value provided
+ public string Convert(object Value, object[] Parents, int Depth, Psd1Converter Converter)
+ {
+ object[] newParents = new object[] { Value };
+
+ if (Parents != null)
+ {
+ if (Parents.Contains(Value))
+ return $"'{Value.GetType().FullName} (recursed)'";
+ if (Converter.MaxDepth > 0 && Parents.Length >= Converter.MaxDepth)
+ return $"'{Value.GetType().FullName}'";
+
+ List newParentList = new List();
+ newParentList.AddRange(Parents);
+ newParentList.Add(Value);
+ newParents = newParentList.ToArray();
+ }
+
+ PSObject value = PSObject.AsPSObject(Value);
+
+ string indent = new string(' ', Depth * 4);
+ string newIndent = new string(' ', (Depth + 1) * 4);
+
+ StringBuilder sb = new StringBuilder();
+ sb.AppendLine("@{");
+
+ foreach (PSPropertyInfo property in value.Properties)
+ {
+ object propValue = null;
+ // Reading from ScriptProperties or CodeProperties can fail
+ try { propValue = property.Value; }
+ catch { }
+
+
+ if (property.Name == "Root" || property.Name == "Parent" || property.Name == "Directory")
+ propValue = $"{propValue}";
+
+ sb.AppendLine($"{newIndent}{CodeGeneration.EscapeSingleQuotedStringContent(LanguagePrimitives.ConvertTo(property.Name))} = {DataHost.Convert(propValue, newParents, Depth + 1, Converter)}");
+ }
+
+ sb.Append($"{indent}}}");
+ return sb.ToString();
+ }
+ }
+}
diff --git a/library/PSFramework/Data/Converters/GuidConverter.cs b/library/PSFramework/Data/Converters/GuidConverter.cs
new file mode 100644
index 00000000..f40f20ee
--- /dev/null
+++ b/library/PSFramework/Data/Converters/GuidConverter.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Data.Converters
+{
+ ///
+ /// Converts data structures into PSD1 Strings
+ ///
+ public class GuidConverter : IPsd1Converter
+ {
+ ///
+ /// Converts to psd1 string
+ ///
+ /// The object to convert
+ /// The indentation level
+ /// The conversion runtime object, including its settings.
+ /// The parent objects. Used in complex data types to prevent infinite recursion.
+ /// The converted PSD1 representation of the value provided
+ public string Convert(object Value, object[] Parents, int Depth, Psd1Converter Converter)
+ {
+ return $"'{Value.ToString()}'";
+ }
+ }
+}
diff --git a/library/PSFramework/Data/Converters/HashtableConverter.cs b/library/PSFramework/Data/Converters/HashtableConverter.cs
new file mode 100644
index 00000000..00e8ee17
--- /dev/null
+++ b/library/PSFramework/Data/Converters/HashtableConverter.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Management.Automation.Language;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Data.Converters
+{
+ ///
+ /// Converts data structures into PSD1 Strings
+ ///
+ public class HashtableConverter : IPsd1Converter
+ {
+ ///
+ /// Converts to psd1 string
+ ///
+ /// The object to convert
+ /// The indentation level
+ /// The conversion runtime object, including its settings.
+ /// The parent objects. Used in complex data types to prevent infinite recursion.
+ /// The converted PSD1 representation of the value provided
+ public string Convert(object Value, object[] Parents, int Depth, Psd1Converter Converter)
+ {
+ object[] newParents = new object[] { Value };
+
+ if (Parents != null)
+ {
+ if (Parents.Contains(Value))
+ return "'System.Collections.Hashtable (recursed)'";
+ if (Converter.MaxDepth > 0 && Parents.Length >= Converter.MaxDepth)
+ return "'System.Collections.Hashtable'";
+
+ List newParentList = new List();
+ newParentList.AddRange(Parents);
+ newParentList.Add(Value);
+ newParents = newParentList.ToArray();
+ }
+
+ IDictionary value = (IDictionary)Value;
+
+ string indent = new string(' ', Depth * 4);
+ string newIndent = new string(' ', (Depth + 1) * 4);
+
+ StringBuilder sb = new StringBuilder();
+ sb.AppendLine("@{");
+
+ foreach (object key in value.Keys)
+ sb.AppendLine($"{newIndent}{CodeGeneration.EscapeSingleQuotedStringContent(LanguagePrimitives.ConvertTo(key))} = {DataHost.Convert(value[key], newParents, Depth + 1, Converter)}");
+
+ sb.Append($"{indent}}}");
+ return sb.ToString();
+ }
+ }
+}
diff --git a/library/PSFramework/Data/Converters/IntConverter.cs b/library/PSFramework/Data/Converters/IntConverter.cs
new file mode 100644
index 00000000..484836d8
--- /dev/null
+++ b/library/PSFramework/Data/Converters/IntConverter.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Data.Converters
+{
+ ///
+ /// Converts data structures into PSD1 Strings
+ ///
+ public class IntConverter : IPsd1Converter
+ {
+ ///
+ /// Converts to psd1 string
+ ///
+ /// The object to convert
+ /// The indentation level
+ /// The conversion runtime object, including its settings.
+ /// The parent objects. Used in complex data types to prevent infinite recursion.
+ /// The converted PSD1 representation of the value provided
+ public string Convert(object Value, object[] Parents, int Depth, Psd1Converter Converter)
+ {
+ return $"{Value}";
+ }
+ }
+}
diff --git a/library/PSFramework/Data/Converters/PSDriveInfoConverter.cs b/library/PSFramework/Data/Converters/PSDriveInfoConverter.cs
new file mode 100644
index 00000000..6cfd3116
--- /dev/null
+++ b/library/PSFramework/Data/Converters/PSDriveInfoConverter.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Data.Converters
+{
+ ///
+ /// Converts data structures into PSD1 Strings
+ ///
+ public class PSDriveInfoConverter : IPsd1Converter
+ {
+ ///
+ /// Converts to psd1 string
+ ///
+ /// The object to convert
+ /// The indentation level
+ /// The conversion runtime object, including its settings.
+ /// The parent objects. Used in complex data types to prevent infinite recursion.
+ /// The converted PSD1 representation of the value provided
+ public string Convert(object Value, object[] Parents, int Depth, Psd1Converter Converter)
+ {
+ return $"'{((PSDriveInfo)Value).Name}'";
+ }
+ }
+}
diff --git a/library/PSFramework/Data/Converters/PSObjectConverter.cs b/library/PSFramework/Data/Converters/PSObjectConverter.cs
new file mode 100644
index 00000000..3b0f42cc
--- /dev/null
+++ b/library/PSFramework/Data/Converters/PSObjectConverter.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Management.Automation;
+using System.Management.Automation.Language;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Data.Converters
+{
+ ///
+ /// Converts data structures into PSD1 Strings
+ ///
+ public class PSObjectConverter : IPsd1Converter
+ {
+ ///
+ /// Converts to psd1 string
+ ///
+ /// The object to convert
+ /// The indentation level
+ /// The conversion runtime object, including its settings.
+ /// The parent objects. Used in complex data types to prevent infinite recursion.
+ /// The converted PSD1 representation of the value provided
+ public string Convert(object Value, object[] Parents, int Depth, Psd1Converter Converter)
+ {
+ object[] newParents = new object[] { Value };
+
+ if (Parents != null)
+ {
+ if (Parents.Contains(Value))
+ return $"'{Value.GetType().FullName} (recursed)'";
+ if (Converter.MaxDepth > 0 && Parents.Length >= Converter.MaxDepth)
+ return $"'{Value.GetType().FullName}'";
+
+ List newParentList = new List();
+ newParentList.AddRange(Parents);
+ newParentList.Add(Value);
+ newParents = newParentList.ToArray();
+ }
+
+ PSObject value = PSObject.AsPSObject(Value);
+
+ string indent = new string(' ', Depth * 4);
+ string newIndent = new string(' ', (Depth + 1) * 4);
+
+ StringBuilder sb = new StringBuilder();
+ sb.AppendLine("@{");
+
+ foreach (PSPropertyInfo property in value.Properties)
+ {
+ object propValue = null;
+ // Reading from ScriptProperties or CodeProperties can fail
+ try { propValue = property.Value; }
+ catch { }
+
+ sb.AppendLine($"{newIndent}{CodeGeneration.EscapeSingleQuotedStringContent(LanguagePrimitives.ConvertTo(property.Name))} = {DataHost.Convert(propValue, newParents, Depth + 1, Converter)}");
+ }
+
+ sb.Append($"{indent}}}");
+ return sb.ToString();
+ }
+ }
+}
diff --git a/library/PSFramework/Data/Converters/PSProviderInfoConverter.cs b/library/PSFramework/Data/Converters/PSProviderInfoConverter.cs
new file mode 100644
index 00000000..6bdbe91e
--- /dev/null
+++ b/library/PSFramework/Data/Converters/PSProviderInfoConverter.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Data.Converters
+{
+ ///
+ /// Converts data structures into PSD1 Strings
+ ///
+ public class PSProviderInfoConverter : IPsd1Converter
+ {
+ ///
+ /// Converts to psd1 string
+ ///
+ /// The object to convert
+ /// The indentation level
+ /// The conversion runtime object, including its settings.
+ /// The parent objects. Used in complex data types to prevent infinite recursion.
+ /// The converted PSD1 representation of the value provided
+ public string Convert(object Value, object[] Parents, int Depth, Psd1Converter Converter)
+ {
+ return $"'{((ProviderInfo)Value).Name}'";
+ }
+ }
+}
diff --git a/library/PSFramework/Data/Converters/ScriptblockConverter.cs b/library/PSFramework/Data/Converters/ScriptblockConverter.cs
new file mode 100644
index 00000000..15528b2b
--- /dev/null
+++ b/library/PSFramework/Data/Converters/ScriptblockConverter.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Management.Automation.Language;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Data.Converters
+{
+ ///
+ /// Converts data structures into PSD1 Strings
+ ///
+ internal class ScriptblockConverter : IPsd1Converter
+ {
+ ///
+ /// Converts to psd1 string
+ ///
+ /// The object to convert
+ /// The indentation level
+ /// The conversion runtime object, including its settings.
+ /// The parent objects. Used in complex data types to prevent infinite recursion.
+ /// The converted PSD1 representation of the value provided
+ public string Convert(object Value, object[] Parents, int Depth, Psd1Converter Converter)
+ {
+ ScriptBlock code = (ScriptBlock)Value;
+ StringBuilder sb = new StringBuilder();
+ sb.AppendLine("{");
+ foreach (string line in code.Ast.Extent.Text.Substring(1, code.Ast.Extent.Text.Length - 2).Split('\n'))
+ sb.AppendLine($" {line}");
+ sb.Append($"{new String(' ', 4 * Depth)}}}");
+
+ return sb.ToString();
+ }
+ }
+}
diff --git a/library/PSFramework/Data/Converters/SwitchConverter.cs b/library/PSFramework/Data/Converters/SwitchConverter.cs
new file mode 100644
index 00000000..0bb11b2d
--- /dev/null
+++ b/library/PSFramework/Data/Converters/SwitchConverter.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Data.Converters
+{
+ ///
+ /// Converts data structures into PSD1 Strings
+ ///
+ public class SwitchConverter : IPsd1Converter
+ {
+ ///
+ /// Converts to psd1 string
+ ///
+ /// The object to convert
+ /// The indentation level
+ /// The conversion runtime object, including its settings.
+ /// The parent objects. Used in complex data types to prevent infinite recursion.
+ /// The converted PSD1 representation of the value provided
+ public string Convert(object Value, object[] Parents, int Depth, Psd1Converter Converter)
+ {
+ return $"${((SwitchParameter)Value).ToBool()}";
+ }
+ }
+}
diff --git a/library/PSFramework/Data/Converters/TextConverter.cs b/library/PSFramework/Data/Converters/TextConverter.cs
new file mode 100644
index 00000000..5576e645
--- /dev/null
+++ b/library/PSFramework/Data/Converters/TextConverter.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Management.Automation.Language;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Data.Converters
+{
+ ///
+ /// Converts data structures into PSD1 Strings
+ ///
+ public class TextConverter : IPsd1Converter
+ {
+ ///
+ /// Converts to psd1 string
+ ///
+ /// The object to convert
+ /// The indentation level
+ /// The conversion runtime object, including its settings.
+ /// The parent objects. Used in complex data types to prevent infinite recursion.
+ /// The converted PSD1 representation of the value provided
+ public string Convert(object Value, object[] Parents, int Depth, Psd1Converter Converter)
+ {
+ return $"'{Converter.EscapeQuotes(Value)}'";
+ }
+ }
+}
diff --git a/library/PSFramework/Data/Converters/TimeSpanConverter.cs b/library/PSFramework/Data/Converters/TimeSpanConverter.cs
new file mode 100644
index 00000000..6a732ce1
--- /dev/null
+++ b/library/PSFramework/Data/Converters/TimeSpanConverter.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Data.Converters
+{
+ ///
+ /// Converts data structures into PSD1 Strings
+ ///
+ public class TimeSpanConverter : IPsd1Converter
+ {
+ ///
+ /// Converts to psd1 string
+ ///
+ /// The object to convert
+ /// The indentation level
+ /// The conversion runtime object, including its settings.
+ /// The parent objects. Used in complex data types to prevent infinite recursion.
+ /// The converted PSD1 representation of the value provided
+ public string Convert(object Value, object[] Parents, int Depth, Psd1Converter Converter)
+ {
+ return String.Format("'{0:c}'", Value);
+ }
+ }
+}
diff --git a/library/PSFramework/Data/Converters/TypeInfoConverter.cs b/library/PSFramework/Data/Converters/TypeInfoConverter.cs
new file mode 100644
index 00000000..dd71d0d8
--- /dev/null
+++ b/library/PSFramework/Data/Converters/TypeInfoConverter.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Data.Converters
+{
+ ///
+ /// Converts data structures into PSD1 Strings
+ ///
+ public class TypeInfoConverter : IPsd1Converter
+ {
+ ///
+ /// Converts to psd1 string
+ ///
+ /// The object to convert
+ /// The indentation level
+ /// The conversion runtime object, including its settings.
+ /// The parent objects. Used in complex data types to prevent infinite recursion.
+ /// The converted PSD1 representation of the value provided
+ public string Convert(object Value, object[] Parents, int Depth, Psd1Converter Converter)
+ {
+ return $"'{((TypeInfo)Value).FullName}'";
+ }
+ }
+}
diff --git a/library/PSFramework/Data/Converters/VersionConverter.cs b/library/PSFramework/Data/Converters/VersionConverter.cs
new file mode 100644
index 00000000..2d1d35e5
--- /dev/null
+++ b/library/PSFramework/Data/Converters/VersionConverter.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Data.Converters
+{
+ ///
+ /// Converts data structures into PSD1 Strings
+ ///
+ public class VersionConverter : IPsd1Converter
+ {
+ ///
+ /// Converts to psd1 string
+ ///
+ /// The object to convert
+ /// The indentation level
+ /// The conversion runtime object, including its settings.
+ /// The parent objects. Used in complex data types to prevent infinite recursion.
+ /// The converted PSD1 representation of the value provided
+ public string Convert(object Value, object[] Parents, int Depth, Psd1Converter Converter)
+ {
+ return $"'{Value}'";
+ }
+ }
+}
diff --git a/library/PSFramework/Data/DataHost.cs b/library/PSFramework/Data/DataHost.cs
new file mode 100644
index 00000000..f5400940
--- /dev/null
+++ b/library/PSFramework/Data/DataHost.cs
@@ -0,0 +1,109 @@
+using PSFramework.Data.Converters;
+using System;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Management.Automation;
+using System.Management.Automation.Language;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Data
+{
+ ///
+ /// Global Helper Tools for dealing with Data
+ ///
+ public static class DataHost
+ {
+ ///
+ /// The registered list of PSD1 converters
+ ///
+ public static Dictionary Converters = new Dictionary();
+
+ ///
+ /// The fallback converter to use when all else fails
+ ///
+ public static IPsd1Converter DefaultConverter { get; internal set; }
+
+ ///
+ /// The converter to use for dictionaries, such as Hashtables
+ ///
+ public static IPsd1Converter DictionaryConverter { get; internal set; }
+
+ ///
+ /// Convert an object to the string to insert into a psd1 document.
+ ///
+ /// The value to convert
+ /// The parent objects. Used in complex data types to prevent infinite recursion.
+ /// The current indentation depth. Can be ignored for plain value returns, if you do not need to return multiple lines.
+ /// The conversion runtime object, including its settings.
+ /// The converted PSD1 representation of the value provided
+ public static string Convert(object Value, object[] Parents, int Depth, Psd1Converter Converter)
+ {
+ if (null == Value)
+ return "$null";
+ Converter.WriteVerbose($"Processing {Value.GetType().FullName}");
+ if (Value as Enum != null)
+ return $"'{CodeGeneration.EscapeSingleQuotedStringContent(Value.ToString())}'";
+ if (Converters.ContainsKey(Value.GetType()))
+ return Converters[Value.GetType()].Convert(Value, Parents, Depth, Converter);
+
+ if (typeof(IDictionary).IsAssignableFrom(Value.GetType()))
+ return DictionaryConverter.Convert(Value, Parents, Depth, Converter);
+
+ // This will ensure both parent types and Interfaces apply properly
+ foreach (Type key in Converters.Keys)
+ if (key.IsAssignableFrom(Value.GetType()))
+ return Converters[key].Convert(Value, Parents, Depth, Converter);
+
+ // This will be ugly. Probably.
+ return DefaultConverter.Convert(Value, Parents, Depth, Converter);
+ }
+
+ ///
+ /// Resets the PSD1 Converters to their default state
+ ///
+ public static void ResetConverters()
+ {
+ Converters = new Dictionary();
+ Initialize();
+ }
+
+ internal static void Initialize()
+ {
+ Converters[typeof(Int16)] = new IntConverter();
+ Converters[typeof(Int32)] = new IntConverter();
+ Converters[typeof(Int64)] = new IntConverter();
+ Converters[typeof(UInt16)] = new IntConverter();
+ Converters[typeof(UInt32)] = new IntConverter();
+ Converters[typeof(UInt64)] = new IntConverter();
+ Converters[typeof(Double)] = new DoubleConverter();
+ Converters[typeof(float)] = new DoubleConverter();
+ Converters[typeof(Boolean)] = new BoolConverter();
+ Converters[typeof(SwitchParameter)] = new SwitchConverter();
+ Converters[typeof(DBNull)] = new DBNullConverter();
+ Converters[typeof(DateTime)] = new DateTimeConverter();
+ Converters[typeof(TimeSpan)] = new TimeSpanConverter();
+ Converters[typeof(Guid)] = new GuidConverter();
+ Converters[typeof(Version)] = new VersionConverter();
+ Converters[typeof(String)] = new TextConverter();
+ Converters[typeof(Char)] = new TextConverter();
+ Converters[typeof(Uri)] = new TextConverter();
+ Converters[typeof(Assembly)] = new AssemblyConverter();
+ Converters[typeof(TypeInfo)] = new TypeInfoConverter();
+ Converters[typeof(ProviderInfo)] = new PSProviderInfoConverter();
+ Converters[typeof(PSDriveInfo)] = new PSDriveInfoConverter();
+ Converters[typeof(ICollection)] = new ArrayConverter();
+ Converters[typeof(FileInfo)] = new FileSystemInfoConverter();
+ Converters[typeof(DirectoryInfo)] = new FileSystemInfoConverter();
+ Converters[typeof(ScriptBlock)] = new ScriptblockConverter();
+
+ DictionaryConverter = new HashtableConverter();
+ Converters[typeof(PSCustomObject)] = new PSObjectConverter();
+ DefaultConverter = new PSObjectConverter();
+ }
+ }
+}
diff --git a/library/PSFramework/Data/IPsd1Converter.cs b/library/PSFramework/Data/IPsd1Converter.cs
new file mode 100644
index 00000000..32b884e8
--- /dev/null
+++ b/library/PSFramework/Data/IPsd1Converter.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Data
+{
+ ///
+ /// Ruleset for types implementing PSD1 Conversion
+ ///
+ public interface IPsd1Converter
+ {
+ ///
+ /// Convert an object to the string to insert into a psd1 document.
+ ///
+ /// The value to convert
+ /// The parent objects. Used in complex data types to prevent infinite recursion.
+ /// The current indentation depth. Can be ignored for plain value returns, if you do not need to return multiple lines.
+ /// The conversion runtime object, including its settings.
+ /// The converted PSD1 representation of the value provided
+ string Convert(object Value, object[] Parents, int Depth, Psd1Converter Converter);
+ }
+}
diff --git a/library/PSFramework/Data/Psd1Converter.cs b/library/PSFramework/Data/Psd1Converter.cs
new file mode 100644
index 00000000..ba33d5db
--- /dev/null
+++ b/library/PSFramework/Data/Psd1Converter.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Management.Automation.Language;
+using System.Text;
+using System.Threading.Tasks;
+using static System.Net.Mime.MediaTypeNames;
+
+namespace PSFramework.Data
+{
+ ///
+ /// The PSD1 conversion runtime object.
+ /// Use this to transport any settings conversion plugins require.
+ ///
+ public class Psd1Converter
+ {
+ ///
+ /// The reference to the PowerShell command running this conversion.
+ /// Can be used to directly interact with the current runtime, send messages or throw errors.
+ /// May be null.
+ ///
+ public Cmdlet Cmdlet;
+
+ ///
+ /// How deeply nested are we willing to delve into sub-properties?
+ /// By default, this is only respected for PSObjects or Dictionaries.
+ ///
+ public int MaxDepth = -1;
+
+ ///
+ /// Extra configuration settings.
+ /// This will be ignored by the default, builtin converters, but external ones may use this to transport and implement settings.
+ ///
+ public Hashtable Config = new Hashtable(StringComparer.OrdinalIgnoreCase);
+
+ ///
+ /// Enable verbose messages.
+ ///
+ public bool EnableVerbose;
+
+ ///
+ /// Convert some object to PSD1
+ ///
+ /// The object to convert to PSD1
+ /// A PSD1-string
+ public string Convert(object Value)
+ {
+ return DataHost.Convert(Value, null, 0, this);
+ }
+
+ ///
+ /// Convert some object to PSD1
+ ///
+ /// The object to convert to PSD1
+ /// How deeply indented do we start?
+ /// What parent objects came before us? Helps prevent infinite recursion.
+ /// A PSD1-string
+ public string Convert(object Value, object[] Parents, int Depth)
+ {
+ return DataHost.Convert(Value, Parents, Depth, this);
+ }
+
+ ///
+ /// Send a message (maybe)
+ ///
+ ///
+ public void WriteVerbose(string Message)
+ {
+ if (!EnableVerbose)
+ return;
+
+ if (Cmdlet != null)
+ {
+ Cmdlet.WriteVerbose(Message);
+ return;
+ }
+
+ using (PowerShell runtime = PowerShell.Create(RunspaceMode.CurrentRunspace))
+ {
+ runtime.AddCommand("Microsoft.PowerShell.Utility\\Write-Verbose").AddArgument(Message).Invoke();
+ }
+ }
+
+ ///
+ /// Escapes a string's single quotes of all flavors.
+ ///
+ /// The text to escape
+ /// A properly escaped string
+ public string EscapeQuotes(string Text)
+ {
+ return CodeGeneration.EscapeSingleQuotedStringContent(Text);
+ }
+
+ ///
+ /// Escapes a string's single quotes of all flavors.
+ /// Ensures content is converted to text using PowerShell rules first.
+ ///
+ /// The text to escape
+ /// A properly escaped string
+ public string EscapeQuotes(object Value)
+ {
+ return CodeGeneration.EscapeSingleQuotedStringContent(LanguagePrimitives.ConvertTo(Value));
+ }
+ }
+}
diff --git a/library/PSFramework/Logging/TxtBuilder.cs b/library/PSFramework/Logging/TxtBuilder.cs
new file mode 100644
index 00000000..686f70a7
--- /dev/null
+++ b/library/PSFramework/Logging/TxtBuilder.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+namespace PSFramework.Logging
+{
+ ///
+ /// Helper class to properly format logging messages for the TXT-Variant of the logfile provider
+ ///
+ public class TxtBuilder
+ {
+ ///
+ /// The text pattern provided by the user, describing the way the logfile is supposed to look.
+ ///
+ public string Text { get; private set; }
+
+ ///
+ /// The properties that exist within the text pattern. Used to optimize the content replacement process.
+ ///
+ private List Properties = new List();
+
+ ///
+ /// Create an empty text-builder
+ ///
+ public TxtBuilder() { }
+
+ ///
+ /// Create a text-builder preconfigured with a text to build
+ ///
+ /// The text to build. Use "%PropertyName%" to offer placeholders that later values get inserted into.
+ public TxtBuilder(string Text)
+ {
+ Load(Text);
+ }
+
+ ///
+ /// Load a new text to build.
+ ///
+ /// The text to build. Use "%PropertyName%" to offer placeholders that later values get inserted into.
+ public void Load(string Text)
+ {
+ if (this.Text == Text)
+ return;
+ List properties = new List();
+ foreach (Match match in Regex.Matches(Text, "%([^\\s%]+)%"))
+ properties.Add(match.Groups[1].Value);
+ Properties = properties;
+ this.Text = Text;
+ }
+
+ ///
+ /// Take a message object and build it into a string, ready for logging
+ ///
+ /// The message object to build.
+ /// The finished message, ready for the logfile.
+ public string Convert(PSObject Message)
+ {
+ string newMessage = Text;
+ foreach (string property in Properties)
+ if (Message.Properties[property] != null)
+ newMessage = newMessage.Replace($"%{property}%", LanguagePrimitives.ConvertTo(Message.Properties[property].Value));
+ return newMessage;
+ }
+ }
+}
diff --git a/library/PSFramework/Message/MessageHost.cs b/library/PSFramework/Message/MessageHost.cs
index 721e1ba2..3cc81508 100644
--- a/library/PSFramework/Message/MessageHost.cs
+++ b/library/PSFramework/Message/MessageHost.cs
@@ -81,6 +81,11 @@ public static class MessageHost
///
public static bool EnableMessageTimestamp = true;
+ ///
+ /// Include the message target (if present) in verbose message output
+ ///
+ public static bool EnableMessageTarget = false;
+
///
/// Include the level of a message in verbose message output
///
@@ -106,6 +111,11 @@ public static class MessageHost
///
public static bool NoColor = false;
+ ///
+ /// The format used in messages written on screen
+ ///
+ public static string TimeFormat = "HH:mm:ss";
+
///
/// Define the message prefix value for the critical level
///
diff --git a/library/PSFramework/Meta/Scope.cs b/library/PSFramework/Meta/Scope.cs
new file mode 100644
index 00000000..a28e717d
--- /dev/null
+++ b/library/PSFramework/Meta/Scope.cs
@@ -0,0 +1,308 @@
+using PSFramework.Utility;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Meta
+{
+ ///
+ /// Wrapper exposing a PowerShell scope and all its content.
+ ///
+ public class Scope
+ {
+ internal object _Scope;
+
+ ///
+ /// What kind of scope it is.
+ ///
+ public string Type { get; internal set; }
+
+ #region Constructors
+ ///
+ /// Creates an empty scope object
+ ///
+ internal Scope() { }
+
+ ///
+ /// Creates a scope object, wrapping around a previously reflected PS scope object.
+ ///
+ /// The raw scope object, as obtained from the PowerShell engine via reflection.
+ internal Scope(object RawScope)
+ {
+ _Scope = RawScope;
+ VerifyType();
+ }
+ #endregion Constructors
+
+ #region Factory
+ ///
+ /// Get a representation of the global scope.
+ ///
+ /// a representation of the global scope
+ public static Scope Global()
+ {
+ Scope temp = new Scope();
+ object sessionState = UtilityHost.GetPrivateProperty("SessionState", UtilityHost.GetExecutionContextFromTLS());
+ object sessionStateInternal = UtilityHost.GetPrivateProperty("Internal", sessionState);
+ temp._Scope = UtilityHost.GetPrivateProperty("GlobalScope", sessionStateInternal);
+ temp.Type = "Global";
+ return temp;
+ }
+
+ ///
+ /// Get a representation of the module scope.
+ ///
+ /// a representation of the module scope
+ public static Scope Module()
+ {
+ Scope temp = new Scope();
+ object sessionState = UtilityHost.GetPrivateProperty("SessionState", UtilityHost.GetExecutionContextFromTLS());
+ object sessionStateInternal = UtilityHost.GetPrivateProperty("Internal", sessionState);
+ temp._Scope = UtilityHost.GetPrivateProperty("ModuleScope", sessionStateInternal);
+ temp.Type = "Module";
+ return temp;
+ }
+
+ ///
+ /// Get a representation of the script scope.
+ ///
+ /// a representation of the script scope
+ public static Scope Script()
+ {
+ Scope temp = new Scope();
+ object sessionState = UtilityHost.GetPrivateProperty("SessionState", UtilityHost.GetExecutionContextFromTLS());
+ object sessionStateInternal = UtilityHost.GetPrivateProperty("Internal", sessionState);
+ temp._Scope = UtilityHost.GetPrivateProperty("ScriptScope", sessionStateInternal);
+ temp.Type = "Script";
+ return temp;
+ }
+
+ ///
+ /// Get a representation of the current scope.
+ ///
+ /// a representation of the current scope
+ public static Scope Current()
+ {
+ Scope temp = new Scope();
+ object sessionState = UtilityHost.GetPrivateProperty("SessionState", UtilityHost.GetExecutionContextFromTLS());
+ object sessionStateInternal = UtilityHost.GetPrivateProperty("Internal", sessionState);
+ temp._Scope = UtilityHost.GetPrivateProperty("CurrentScope", sessionStateInternal);
+ temp.Type = "Current";
+ return temp;
+ }
+ #endregion Factory
+
+ #region Reflection
+ #region Variables
+ private bool _VariablesEnabled;
+ ///
+ /// Enable access to variable content of the scope.
+ ///
+ public void EnableVariables() { _VariablesEnabled = true; }
+
+ ///
+ /// Disable access to variable content of the scope.
+ ///
+ public void DisableVariables() { _VariablesEnabled = false; }
+
+ ///
+ /// The variables contained in the scope
+ ///
+ public Dictionary Variables
+ {
+ get
+ {
+ if (null == _Scope || !_VariablesEnabled)
+ return null;
+ return (Dictionary)UtilityHost.GetPrivateField("_variables", _Scope);
+ }
+ }
+
+ private Dictionary _Variables
+ {
+ get
+ {
+ if (null == _Scope)
+ return null;
+ return (Dictionary)UtilityHost.GetPrivateField("_variables", _Scope);
+ }
+ }
+
+ ///
+ /// Checks whether a specific variable exists in the scope
+ ///
+ /// Name of the variable to scan for
+ /// Whether the variable exists
+ public bool HasVariable(string Name)
+ { return null != _Scope && _Variables.ContainsKey(Name); }
+
+ ///
+ /// Retrieve the value of a variable in the scope.
+ /// Returns null if scope or variable do not exist.
+ ///
+ /// Name of the variable to retrieve.
+ /// The value of the variable.
+ public object GetVariableValue(string Name)
+ {
+ if (null == _Scope)
+ return null;
+ if (!HasVariable(Name))
+ return null;
+ return _Variables[Name].Value;
+ }
+
+ ///
+ /// Change the value of a variable, in effect overwriting it.
+ ///
+ /// The name of the variable to apply
+ /// The value to assign to the variable
+ public void SetVariable(string Name, object Value)
+ {
+ if (null == _Scope)
+ return;
+ _Variables[Name] = new PSVariable(Name, Value);
+ }
+ #endregion Variables
+
+ #region Aliases
+ private bool _AliasesEnabled;
+ ///
+ /// Enable access to alias content of the scope.
+ ///
+ public void EnableAliases() { _AliasesEnabled = true; }
+
+ ///
+ /// Disable access to alias content of the scope.
+ ///
+ public void DisableAliases() { _AliasesEnabled = false; }
+
+ ///
+ /// The aliases of the scope
+ ///
+ public Dictionary Aliases
+ {
+ get
+ {
+ if (null == _Scope || !_AliasesEnabled)
+ return null;
+ return (Dictionary)UtilityHost.GetPrivateField("_alias", _Scope);
+ }
+ }
+ #endregion Aliases
+
+ #region Functions
+ private bool _FunctionsEnabled;
+ ///
+ /// Enable access to function content of the scope.
+ ///
+ public void EnableFunctions() { _FunctionsEnabled = true; }
+
+ ///
+ /// Disable access to function content of the scope.
+ ///
+ public void DisableFunctions() { _FunctionsEnabled = false; }
+
+ ///
+ /// The functions defined within the scope.
+ ///
+ public Dictionary Functions
+ {
+ get
+ {
+ if (null == _Scope || !_FunctionsEnabled)
+ return null;
+ return (Dictionary)UtilityHost.GetPrivateField("_functions", _Scope);
+ }
+ }
+ #endregion Functions
+
+ #region Cmdlets
+ private bool _CmdletsEnabled;
+ ///
+ /// Enable access to cmdlet content of the scope.
+ ///
+ public void EnableCmdlets() { _CmdletsEnabled = true; }
+
+ ///
+ /// Disable access to cmdlet content of the scope.
+ ///
+ public void DisableCmdlets() { _CmdletsEnabled = false; }
+
+ ///
+ /// The Cmdlets contained within the scope
+ ///
+ public Dictionary Cmdlets
+ {
+ get
+ {
+ if (null == _Scope || !_CmdletsEnabled)
+ return null;
+ return (Dictionary)UtilityHost.GetPrivateField("_cmdlets", _Scope);
+ }
+ }
+ #endregion Cmdlets
+
+ #region Drives
+ private bool _DrivesEnabled;
+ ///
+ /// Enable access to PSDrivecontent of the scope.
+ ///
+ public void EnableDrives() { _DrivesEnabled = true; }
+
+ ///
+ /// Disable access to PSDrivecontent of the scope.
+ ///
+ public void DisableDrives() { _DrivesEnabled = false; }
+
+ ///
+ /// The PSDrives contained within the scope
+ ///
+ public Dictionary Drives
+ {
+ get
+ {
+ if (null == _Scope || !_DrivesEnabled)
+ return null;
+ return (Dictionary)UtilityHost.GetPrivateField("_drives", _Scope);
+ }
+ }
+ #endregion Drives
+ #endregion Reflection
+
+ ///
+ /// The Parent Scope of the current scope
+ ///
+ public Scope Parent
+ {
+ get
+ {
+ object parentScope = UtilityHost.GetPrivateProperty("Parent", _Scope);
+ if (null == parentScope)
+ return null;
+
+ return new Scope(parentScope);
+ }
+ }
+
+ ///
+ /// Update the assigned Type.
+ /// Intended when generating a scope based on another scope.
+ ///
+ internal void VerifyType()
+ {
+ if (_Scope == Global()._Scope)
+ Type = "Global";
+ else if (Type == "Module")
+ Type = "Module";
+ else if (_Scope == UtilityHost.GetPrivateProperty("ScriptScope", _Scope))
+ Type = "Script";
+ else if (Type == "Current")
+ Type = "Current";
+ else
+ Type = "Unknown";
+ }
+ }
+}
diff --git a/library/PSFramework/Object/ObjectHost.cs b/library/PSFramework/Object/ObjectHost.cs
index 41a58141..f7bf43c1 100644
--- a/library/PSFramework/Object/ObjectHost.cs
+++ b/library/PSFramework/Object/ObjectHost.cs
@@ -29,6 +29,22 @@ public static void AddNoteProperty(PSObject Item, Hashtable Members, bool PreVal
}
}
+ ///
+ /// Add a set of noteproperties to all specified PowerShell objects
+ ///
+ /// The objects to extend
+ /// The set of properties to add
+ /// Whether validation has already been performed and need not be done. Setting this to false will add performance overhead.
+ public static void AddNotePropertyBulk(PSObject[] Items, Hashtable Members, bool PreValidated = true)
+ {
+ foreach (object key in Members.Keys)
+ {
+ PSNoteProperty property = new PSNoteProperty((string)key, Members[key]);
+ foreach (PSObject item in Items)
+ item.Properties.Add(property, PreValidated);
+ }
+ }
+
///
/// Adds a single noteproperty to the specified PowerShell object
///
@@ -42,6 +58,20 @@ public static void AddNoteProperty(PSObject Item, string Name, object Value, boo
Item.Properties.Add(property, PreValidated);
}
+ ///
+ /// Adds a single noteproperty to a lot of specified PowerShell objects
+ ///
+ /// The objects to extend
+ /// The name of the property to add
+ /// The value to assign to the new property
+ /// Whether validation has already been performed and need not be done. Setting this to false will add performance overhead.
+ public static void AddNotePropertyBulk(PSObject[] Items, string Name, object Value, bool PreValidated = true)
+ {
+ PSNoteProperty property = new PSNoteProperty(Name, Value);
+ foreach (PSObject item in Items)
+ item.Properties.Add(property, PreValidated);
+ }
+
///
/// Adds a script-calculated property to the specified PowerShell object
///
@@ -56,6 +86,21 @@ public static void AddScriptProperty(PSObject Item, string Name, ScriptBlock Get
Item.Properties.Add(property, PreValidated);
}
+ ///
+ /// Adds a script-calculated property to the specified PowerShell objects
+ ///
+ /// The objects to extend
+ /// The name of the script-property to add
+ /// The code used to read the value
+ /// (optional) The code used when assigning input to the property.
+ /// Whether validation has already been performed and need not be done. Setting this to false will add performance overhead.
+ public static void AddScriptPropertyBulk(PSObject[] Items, string Name, ScriptBlock Get, ScriptBlock Set = null, bool PreValidated = true)
+ {
+ PSScriptProperty property = new PSScriptProperty(Name, Get, Set);
+ foreach (PSObject item in Items)
+ item.Properties.Add(property, PreValidated);
+ }
+
///
/// Removes a member from the specified PowerShell object
///
@@ -66,6 +111,17 @@ public static void RemoveMember(PSObject Item, string Name)
Item.Members.Remove(Name);
}
+ ///
+ /// Removes a member from the specified PowerShell objects
+ ///
+ /// The objects to remove a member from
+ /// The name of the member to remove.
+ public static void RemoveMemberBulk(PSObject[] Items, string Name)
+ {
+ foreach (PSObject item in Items)
+ item.Members.Remove(Name);
+ }
+
///
/// Adds a script-based method to the specified PowerShell object
///
@@ -79,6 +135,20 @@ public static void AddScriptMethod(PSObject Item, string Name, ScriptBlock Code,
Item.Methods.Add(method, PreValidated);
}
+ ///
+ /// Adds a script-based method to the specified PowerShell objects
+ ///
+ /// The objects to extend
+ /// The name of the method to add.
+ /// The code implementing the logic when the method is called.
+ /// Whether validation has already been performed and need not be done. Setting this to false will add performance overhead.
+ public static void AddScriptMethodBulk(PSObject[] Items, string Name, ScriptBlock Code, bool PreValidated = true)
+ {
+ PSScriptMethod method = new PSScriptMethod(Name, Code);
+ foreach (PSObject item in Items)
+ item.Methods.Add(method, PreValidated);
+ }
+
///
/// Adds several script methods to the specified PowerShell object
///
@@ -93,6 +163,22 @@ public static void AddScriptMethod(PSObject Item, Hashtable Members, bool PreVal
Item.Methods.Add(method, PreValidated);
}
}
+
+ ///
+ /// Adds several script methods to the specified PowerShell objects
+ ///
+ /// The objects to extend
+ /// The methods to add. Provide a hashtable mapping method name to scriptblock with the logic implementing the method.
+ /// Whether validation has already been performed and need not be done. Setting this to false will add performance overhead.
+ public static void AddScriptMethodBulk(PSObject[] Items, Hashtable Members, bool PreValidated = true)
+ {
+ foreach (object key in Members.Keys)
+ {
+ PSScriptMethod method = new PSScriptMethod((string)key, (ScriptBlock)Members[key]);
+ foreach (PSObject item in Items)
+ item.Methods.Add(method, PreValidated);
+ }
+ }
#endregion Member Manipulation
}
}
\ No newline at end of file
diff --git a/library/PSFramework/Object/PsfHashtable.cs b/library/PSFramework/Object/PsfHashtable.cs
index 3bf2acca..921e705c 100644
--- a/library/PSFramework/Object/PsfHashtable.cs
+++ b/library/PSFramework/Object/PsfHashtable.cs
@@ -1,5 +1,7 @@
using System;
using System.Collections;
+using System.Management.Automation;
+using PSFramework.Extension;
namespace PSFramework.Object
{
@@ -13,6 +15,16 @@ public class PsfHashtable : Hashtable
///
private object defaultValue;
+ ///
+ /// Whether in case of unexpected key, the key should be returned, rather than a default value.
+ ///
+ private bool passThru;
+
+ ///
+ /// Scriptblock used to calculate result when providing a key that is not applied to the hashtable.
+ ///
+ private ScriptBlock calculator;
+
///
/// Creates a new, empty psfhashtable
///
@@ -42,6 +54,25 @@ public void SetDefaultValue(object Value)
defaultValue = Value;
}
+ ///
+ /// Enables the PassThru behavior, where an unexpected key gets returned as the default action
+ ///
+ public void EnablePassthru()
+ { passThru = true; }
+
+ ///
+ /// Disables the PassThru behavior, where an unexpected key would get returned as the default action
+ ///
+ public void DisablePassThru()
+ { passThru = false; }
+
+ ///
+ /// Sets the scriptblock used to calculate the results for keys, that are not registered with the hashtable
+ ///
+ /// The logic doing the calculating. Provide null to disable.
+ public void SetCalculator(ScriptBlock Calculator)
+ { this.calculator = Calculator; }
+
///
/// Create a copy of the current PsfHashtable, including its default value. The default value will be the same instance of an object.
///
@@ -50,6 +81,8 @@ public override object Clone()
{
PsfHashtable temp = (PsfHashtable)base.Clone();
temp.SetDefaultValue(defaultValue);
+ if (passThru)
+ temp.EnablePassthru();
return temp;
}
@@ -64,7 +97,13 @@ public override object this[object key]
get
{
if (!ContainsKey(key))
+ {
+ if (calculator != null)
+ return calculator.DoInvokeReturnAsIs(false, 2, key, key, this, new object[] { key });
+ if (passThru)
+ return key;
return defaultValue;
+ }
return base[key];
}
set => base[key] = value;
diff --git a/library/PSFramework/PSFCore/PSFCoreHost.cs b/library/PSFramework/PSFCore/PSFCoreHost.cs
index 896dc423..fcee2d0c 100644
--- a/library/PSFramework/PSFCore/PSFCoreHost.cs
+++ b/library/PSFramework/PSFCore/PSFCoreHost.cs
@@ -40,6 +40,7 @@ public static void Initialize()
_Initialized = true;
Runspace.RunspaceHost.StartRbvTimer();
+ Data.DataHost.Initialize();
// Initialization logic goes here
}
private static bool _Initialized = false;
diff --git a/library/PSFramework/PSFramework.csproj b/library/PSFramework/PSFramework.csproj
index e57a127a..7deebd98 100644
--- a/library/PSFramework/PSFramework.csproj
+++ b/library/PSFramework/PSFramework.csproj
@@ -61,9 +61,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -111,6 +136,7 @@
+
@@ -134,6 +160,7 @@
+
@@ -141,6 +168,8 @@
+
+
@@ -153,9 +182,15 @@
+
+
+
+
+
+
diff --git a/library/PSFramework/Parameter/PathDirectorySingleParameter.cs b/library/PSFramework/Parameter/PathDirectorySingleParameter.cs
new file mode 100644
index 00000000..8fa31cec
--- /dev/null
+++ b/library/PSFramework/Parameter/PathDirectorySingleParameter.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Parameter
+{
+ ///
+ /// Parameterclass that accepts a single directory
+ ///
+ public class PathDirectorySingleParameter : PathFileSystemSingleParameterBase
+ {
+ #region Constructors
+ ///
+ /// Processes a single string as a single directory.
+ ///
+ /// The path to process
+ public PathDirectorySingleParameter(string Path)
+ {
+ InputObject = Path;
+ Apply(Path, false, true);
+ }
+
+ ///
+ /// Processes a single DirectoryInfo item as a single directory.
+ ///
+ /// The path to process
+ public PathDirectorySingleParameter(DirectoryInfo Path) : this(Path.FullName) { InputObject = Path; }
+
+ ///
+ /// Processes a single Uri as a single directory.
+ ///
+ /// The path to process
+ public PathDirectorySingleParameter(Uri Path) : this(Path.OriginalString) { InputObject = Path; }
+
+ ///
+ /// Processes a single object as a single directory.
+ ///
+ /// The path to process
+ public PathDirectorySingleParameter(object Path)
+ {
+ InputObject = Path;
+ string actualpath;
+ try { actualpath = LanguagePrimitives.ConvertTo(PathFileSystemParameterBase.GetObject(Path)); }
+ catch (Exception e) { throw new ArgumentException($"Failed to process {Path}! Error converting to string: {e.Message}", e); }
+
+ Apply(actualpath, false, true);
+ }
+ #endregion Constructors
+
+ #region Operators
+ ///
+ /// Implicitly convert to string.
+ ///
+ /// The path to convert
+ public static implicit operator string(PathDirectorySingleParameter Path)
+ {
+ return Path.Path;
+ }
+ ///
+ /// Implicitly convert to DirectoryInfo.
+ ///
+ /// The path to convert
+ public static implicit operator DirectoryInfo(PathDirectorySingleParameter Path)
+ {
+ return new DirectoryInfo(Path.Path);
+ }
+ ///
+ /// Implicitly convert DirectoryInfo to PathDirectorySingleParameter.
+ ///
+ /// The path to convert
+
+ public static implicit operator PathDirectorySingleParameter(DirectoryInfo Info)
+ {
+ return new PathDirectorySingleParameter(Info);
+ }
+ ///
+ /// Implicitly convert to FileSystemInfo.
+ ///
+ /// The path to convert
+
+ public static implicit operator FileSystemInfo(PathDirectorySingleParameter Path)
+ {
+ return new DirectoryInfo(Path.Path);
+ }
+ #endregion Operators
+ }
+}
diff --git a/library/PSFramework/Parameter/PathFileSingleParameter.cs b/library/PSFramework/Parameter/PathFileSingleParameter.cs
new file mode 100644
index 00000000..56f904f1
--- /dev/null
+++ b/library/PSFramework/Parameter/PathFileSingleParameter.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Parameter
+{
+ ///
+ /// Parameterclass accepting/resolving to a single, fully-resolved path to a file.
+ ///
+ public class PathFileSingleParameter : PathFileSystemSingleParameterBase
+ {
+ #region Constructors
+ ///
+ /// Processes a single string as a single file.
+ ///
+ /// The path to process
+ public PathFileSingleParameter(string Path) {
+ InputObject = Path;
+ Apply(Path, true, false);
+ }
+
+ ///
+ /// Processes a single FileInfo item as a single file.
+ ///
+ /// The path to process
+ public PathFileSingleParameter(FileInfo Path) :this(Path.FullName) { InputObject = Path; }
+
+ ///
+ /// Processes a single Uri as a single file.
+ ///
+ /// The path to process
+ public PathFileSingleParameter(Uri Path) :this(Path.OriginalString) { InputObject = Path; }
+
+ ///
+ /// Processes a single object as a single file.
+ ///
+ /// The path to process
+ public PathFileSingleParameter(object Path)
+ {
+ InputObject = Path;
+ string actualpath;
+ try { actualpath = LanguagePrimitives.ConvertTo(PathFileSystemParameterBase.GetObject(Path)); }
+ catch (Exception e) { throw new ArgumentException($"Failed to process {Path}! Error converting to string: {e.Message}", e); }
+
+ Apply(actualpath, true, false);
+ }
+ #endregion Constructors
+
+ #region Implicit Casts
+ ///
+ /// Implicitly convert to string.
+ ///
+ /// The path to convert
+ public static implicit operator string(PathFileSingleParameter Path)
+ {
+ return Path.Path;
+ }
+ ///
+ /// Implicitly convert to FileInfo.
+ ///
+ /// The path to convert
+ public static implicit operator FileInfo(PathFileSingleParameter Path)
+ {
+ return new FileInfo(Path.Path);
+ }
+ ///
+ /// Implicitly convert FileInfo to PathFileSingle.
+ ///
+ /// The path to convert
+
+ public static implicit operator PathFileSingleParameter(FileInfo Info)
+ {
+ return new PathFileSingleParameter(Info);
+ }
+ ///
+ /// Implicitly convert to FileSystemInfo.
+ ///
+ /// The path to convert
+
+ public static implicit operator FileSystemInfo(PathFileSingleParameter Path)
+ {
+ return new FileInfo(Path.Path);
+ }
+ #endregion Implicit Casts
+ }
+}
diff --git a/library/PSFramework/Parameter/PathFileSystemParameterBase.cs b/library/PSFramework/Parameter/PathFileSystemParameterBase.cs
index 7791efaf..d07ad0b8 100644
--- a/library/PSFramework/Parameter/PathFileSystemParameterBase.cs
+++ b/library/PSFramework/Parameter/PathFileSystemParameterBase.cs
@@ -132,7 +132,7 @@ internal List ResolveFileSystemPath(string Path, bool IncludeFile, bool
return paths;
}
- internal object GetObject(object Input)
+ internal static object GetObject(object Input)
{
if (_PropertyMapping.Count == 0)
return Input;
@@ -190,7 +190,7 @@ internal object GetObject(object Input)
///
public override string ToString()
{
- return String.Join(", ", this);
+ return String.Join(", ", this.ToArray());
}
}
}
diff --git a/library/PSFramework/Parameter/PathFileSystemSingleParameter.cs b/library/PSFramework/Parameter/PathFileSystemSingleParameter.cs
new file mode 100644
index 00000000..f10345a0
--- /dev/null
+++ b/library/PSFramework/Parameter/PathFileSystemSingleParameter.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Parameter
+{
+ ///
+ /// Parameter Class that maps to a single file or folder
+ ///
+ public class PathFileSystemSingleParameter : PathFileSystemSingleParameterBase
+ {
+ #region Constructors
+ ///
+ /// Processes a single string as a single directory or file.
+ ///
+ /// The path to process
+ public PathFileSystemSingleParameter(string Path)
+ {
+ InputObject = Path;
+ Apply(Path, true, true);
+ }
+
+ ///
+ /// Processes a single DirectoryInfo item as a single directory.
+ ///
+ /// The path to process
+ public PathFileSystemSingleParameter(DirectoryInfo Path) : this(Path.FullName) { InputObject = Path; }
+
+ ///
+ /// Processes a single FileInfo item as a single file.
+ ///
+ /// The path to process
+ public PathFileSystemSingleParameter(FileInfo Path) : this(Path.FullName) { InputObject = Path; }
+
+ ///
+ /// Processes a single Uri as a single directory or file.
+ ///
+ /// The path to process
+ public PathFileSystemSingleParameter(Uri Path) : this(Path.OriginalString) { InputObject = Path; }
+
+ ///
+ /// Processes a single object as a single directory or file.
+ ///
+ /// The path to process
+ public PathFileSystemSingleParameter(object Path)
+ {
+ InputObject = Path;
+ string actualpath;
+ try { actualpath = LanguagePrimitives.ConvertTo(PathFileSystemParameterBase.GetObject(Path)); }
+ catch (Exception e) { throw new ArgumentException($"Failed to process {Path}! Error converting to string: {e.Message}", e); }
+
+ Apply(actualpath, true, true);
+ }
+ #endregion Constructors
+
+ #region Operators
+ ///
+ /// Implicitly convert to string.
+ ///
+ /// The path to convert
+ public static implicit operator string(PathFileSystemSingleParameter Path)
+ {
+ return Path.Path;
+ }
+
+ ///
+ /// Implicitly convert to DirectoryInfo.
+ ///
+ /// The path to convert
+ public static implicit operator DirectoryInfo(PathFileSystemSingleParameter Path)
+ {
+ return new DirectoryInfo(Path.Path);
+ }
+
+ ///
+ /// Implicitly convert to FileInfo.
+ ///
+ /// The path to convert
+ public static implicit operator FileInfo(PathFileSystemSingleParameter Path)
+ {
+ return new FileInfo(Path.Path);
+ }
+
+ ///
+ /// Implicitly convert DirectoryInfo to PathFileSystemSingleParameter.
+ ///
+ /// The path to convert
+ public static implicit operator PathFileSystemSingleParameter(DirectoryInfo Info)
+ {
+ return new PathFileSystemSingleParameter(Info);
+ }
+
+ ///
+ /// Implicitly convert FileInfo to PathFileSystemSingleParameter.
+ ///
+ /// The path to convert
+ public static implicit operator PathFileSystemSingleParameter(FileInfo Info)
+ {
+ return new PathFileSystemSingleParameter(Info);
+ }
+
+ ///
+ /// Implicitly convert to FileSystemInfo.
+ ///
+ /// The path to convert
+ public static implicit operator FileSystemInfo(PathFileSystemSingleParameter Path)
+ {
+ if (File.Exists(Path.Path))
+ return new FileInfo(Path.Path);
+ return new DirectoryInfo(Path.Path);
+ }
+ #endregion Operators
+ }
+}
diff --git a/library/PSFramework/Parameter/PathFileSystemSingleParameterBase.cs b/library/PSFramework/Parameter/PathFileSystemSingleParameterBase.cs
new file mode 100644
index 00000000..356436d0
--- /dev/null
+++ b/library/PSFramework/Parameter/PathFileSystemSingleParameterBase.cs
@@ -0,0 +1,96 @@
+using PSFramework.Utility;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Eventing.Reader;
+using System.IO;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Parameter
+{
+ ///
+ /// Base class for parameter classes mapping to a single filesystem item
+ ///
+ [ParameterClass()]
+ public abstract class PathFileSystemSingleParameterBase : ParameterClass
+ {
+ ///
+ /// The path resolved
+ ///
+ [ParameterContract(ParameterContractType.Field, ParameterContractBehavior.Mandatory)]
+ public string Path;
+
+ ///
+ /// Default text representation of the PathFileSingle type.
+ ///
+ /// Its path.
+ public override string ToString()
+ {
+ return Path;
+ }
+
+ ///
+ /// Resolve the path provided and ensure it exists and is of the proper type!
+ ///
+ /// The path to resolve
+ /// Whether it may be a file
+ /// Whether it may be a directory
+ /// When the path provided cannot be resolved at all.
+ /// When the path provided resolves to multiple items.
+ /// When the path should resolve to a file, but is not a file.
+ /// When the path should resolve to a directory, but is not a directory.
+ internal void Apply(string Path, bool MayBeFile, bool MayBeDirectory)
+ {
+ IEnumerable resolved;
+ try { resolved = (new SessionState()).Path.GetResolvedPSPathFromPSPath(Path).Where(o => o.Provider.Name == "FileSystem").Select(o => o.ProviderPath); }
+ catch (Exception e) { throw new ArgumentException($"Unable to resolve filesystem path: {Path}", e); }
+
+ if (resolved.Count() > 1)
+ throw new InvalidDataException($"Accepting only a single file. Path {Path} resolves to {resolved.Count()} items!");
+ if (resolved.Count() == 0)
+ throw new FileNotFoundException($"Unable to resolve to file, item not found: {Path}");
+
+ if (MayBeFile && !MayBeDirectory && !File.Exists(resolved.First()))
+ throw new FileNotFoundException($"Path {Path} is not a file! (Resolved to {resolved.First()})");
+ if (!MayBeFile && MayBeDirectory && !Directory.Exists(resolved.First()))
+ throw new DirectoryNotFoundException($"Path {Path} is not a directory! (Resolved to {resolved.First()})");
+
+ this.Path = resolved.First();
+ }
+
+ ///
+ /// Verify the specified path exists, process relative paths, but do not apply wildcards.
+ ///
+ /// The path to process
+ /// Whether the path may point to a file
+ /// Whether the path may point to a directory
+ /// Thrown if the item does not exist at all
+ /// When the path should resolve to a file, but is not a file.
+ /// When the path should resolve to a directory, but is not a directory.
+ internal void ApplyLiteral(string Path, bool MayBeFile, bool MayBeDirectory)
+ {
+ string tempPath = UtilityHost.JoinPath((new SessionState()).Path.CurrentFileSystemLocation.ProviderPath, Path);
+
+ if (!File.Exists(tempPath) && !Directory.Exists(tempPath))
+ throw new ItemNotFoundException($"Unable to resolve filesystem path: {Path} (Resolved to {tempPath})");
+
+ if (MayBeFile && !MayBeDirectory && !File.Exists(tempPath))
+ throw new FileNotFoundException($"Path {Path} is not a file! (Resolved to {tempPath})");
+ if (!MayBeFile && MayBeDirectory && !Directory.Exists(tempPath))
+ throw new DirectoryNotFoundException($"Path {Path} is not a directory! (Resolved to {tempPath})");
+
+ this.Path = tempPath;
+ }
+
+ ///
+ /// Implicitly convert to string.
+ ///
+ /// The path to convert
+ public static implicit operator string(PathFileSystemSingleParameterBase Path)
+ {
+ return Path.Path;
+ }
+ }
+}
diff --git a/library/PSFramework/Parameter/PathLiteralDirectorySingleParameter.cs b/library/PSFramework/Parameter/PathLiteralDirectorySingleParameter.cs
new file mode 100644
index 00000000..10560cad
--- /dev/null
+++ b/library/PSFramework/Parameter/PathLiteralDirectorySingleParameter.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Parameter
+{
+ ///
+ /// Parameter class that maps to a single directory.
+ ///
+ public class PathLiteralDirectorySingleParameter : PathFileSystemSingleParameterBase
+ {
+ #region Constructors
+ ///
+ /// Processes a single string as a single directory.
+ ///
+ /// The path to process
+ public PathLiteralDirectorySingleParameter(string Path)
+ {
+ InputObject = Path;
+ ApplyLiteral(Path, false, true);
+ }
+
+ ///
+ /// Processes a single DirectoryInfo item as a single directory.
+ ///
+ /// The path to process
+ public PathLiteralDirectorySingleParameter(DirectoryInfo Path) : this(Path.FullName) { InputObject = Path; }
+
+ ///
+ /// Processes a single Uri as a single directory.
+ ///
+ /// The path to process
+ public PathLiteralDirectorySingleParameter(Uri Path) : this(Path.OriginalString) { InputObject = Path; }
+
+ ///
+ /// Processes a single object as a single directory.
+ ///
+ /// The path to process
+ public PathLiteralDirectorySingleParameter(object Path)
+ {
+ InputObject = Path;
+ string actualpath;
+ try { actualpath = LanguagePrimitives.ConvertTo(PathFileSystemParameterBase.GetObject(Path)); }
+ catch (Exception e) { throw new ArgumentException($"Failed to process {Path}! Error converting to string: {e.Message}", e); }
+
+ ApplyLiteral(actualpath, false, true);
+ }
+ #endregion Constructors
+
+ #region Operators
+ ///
+ /// Implicitly convert to string.
+ ///
+ /// The path to convert
+ public static implicit operator string(PathLiteralDirectorySingleParameter Path)
+ {
+ return Path.Path;
+ }
+
+ ///
+ /// Implicitly convert to DirectoryInfo.
+ ///
+ /// The path to convert
+ public static implicit operator DirectoryInfo(PathLiteralDirectorySingleParameter Path)
+ {
+ return new DirectoryInfo(Path.Path);
+ }
+
+ ///
+ /// Implicitly convert DirectoryInfo to PathLiteralDirectorySingleParameter.
+ ///
+ /// The path to convert
+ public static implicit operator PathLiteralDirectorySingleParameter(DirectoryInfo Info)
+ {
+ return new PathLiteralDirectorySingleParameter(Info);
+ }
+
+ ///
+ /// Implicitly convert to FileSystemInfo.
+ ///
+ /// The path to convert
+ public static implicit operator FileSystemInfo(PathLiteralDirectorySingleParameter Path)
+ {
+ return new DirectoryInfo(Path.Path);
+ }
+ #endregion Operators
+ }
+}
diff --git a/library/PSFramework/Parameter/PathLiteralFileSingleParameter.cs b/library/PSFramework/Parameter/PathLiteralFileSingleParameter.cs
new file mode 100644
index 00000000..b159ed8e
--- /dev/null
+++ b/library/PSFramework/Parameter/PathLiteralFileSingleParameter.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Parameter
+{
+ ///
+ /// Parameter Class that maps to a single file without processing wildcards
+ ///
+ public class PathLiteralFileSingleParameter : PathFileSystemSingleParameterBase
+ {
+ #region Constructors
+ ///
+ /// Processes a single string as a single file.
+ ///
+ /// The path to process
+ public PathLiteralFileSingleParameter(string Path)
+ {
+ InputObject = Path;
+ ApplyLiteral(Path, true, false);
+ }
+
+ ///
+ /// Processes a single FileInfo item as a single file.
+ ///
+ /// The path to process
+ public PathLiteralFileSingleParameter(FileInfo Path) : this(Path.FullName) { InputObject = Path; }
+
+ ///
+ /// Processes a single Uri as a single file.
+ ///
+ /// The path to process
+ public PathLiteralFileSingleParameter(Uri Path) : this(Path.OriginalString) { InputObject = Path; }
+
+ ///
+ /// Processes a single object as a single file.
+ ///
+ /// The path to process
+ public PathLiteralFileSingleParameter(object Path)
+ {
+ InputObject = Path;
+ string actualpath;
+ try { actualpath = LanguagePrimitives.ConvertTo(PathFileSystemParameterBase.GetObject(Path)); }
+ catch (Exception e) { throw new ArgumentException($"Failed to process {Path}! Error converting to string: {e.Message}", e); }
+
+ ApplyLiteral(actualpath, true, false);
+ }
+ #endregion Constructors
+
+ #region Operators
+ ///
+ /// Implicitly convert to string.
+ ///
+ /// The path to convert
+ public static implicit operator string(PathLiteralFileSingleParameter Path)
+ {
+ return Path.Path;
+ }
+
+ ///
+ /// Implicitly convert to FileInfo.
+ ///
+ /// The path to convert
+ public static implicit operator FileInfo(PathLiteralFileSingleParameter Path)
+ {
+ return new FileInfo(Path.Path);
+ }
+
+ ///
+ /// Implicitly convert FileInfo to PathLiteralFileSingleParameter.
+ ///
+ /// The path to convert
+ public static implicit operator PathLiteralFileSingleParameter(FileInfo Info)
+ {
+ return new PathLiteralFileSingleParameter(Info);
+ }
+
+ ///
+ /// Implicitly convert to FileSystemInfo.
+ ///
+ /// The path to convert
+ public static implicit operator FileSystemInfo(PathLiteralFileSingleParameter Path)
+ {
+ return new FileInfo(Path.Path);
+ }
+ #endregion Operators
+ }
+}
diff --git a/library/PSFramework/Parameter/PathLiteralSingleParameter.cs b/library/PSFramework/Parameter/PathLiteralSingleParameter.cs
new file mode 100644
index 00000000..129b567c
--- /dev/null
+++ b/library/PSFramework/Parameter/PathLiteralSingleParameter.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Parameter
+{
+ ///
+ /// Parameter Class that maps to a single file or directory, without resolving wildcards.
+ ///
+ public class PathLiteralSingleParameter : PathFileSystemSingleParameterBase
+ {
+ #region Constructors
+ ///
+ /// Processes a single string as a single directory or file.
+ ///
+ /// The path to process
+ public PathLiteralSingleParameter(string Path)
+ {
+ InputObject = Path;
+ ApplyLiteral(Path, true, true);
+ }
+
+ ///
+ /// Processes a single DirectoryInfo item as a single directory.
+ ///
+ /// The path to process
+ public PathLiteralSingleParameter(DirectoryInfo Path) : this(Path.FullName) { InputObject = Path; }
+
+ ///
+ /// Processes a single FileInfo item as a single file.
+ ///
+ /// The path to process
+ public PathLiteralSingleParameter(FileInfo Path) : this(Path.FullName) { InputObject = Path; }
+
+ ///
+ /// Processes a single Uri as a single directory or file.
+ ///
+ /// The path to process
+ public PathLiteralSingleParameter(Uri Path) : this(Path.OriginalString) { InputObject = Path; }
+
+ ///
+ /// Processes a single object as a single directory or file.
+ ///
+ /// The path to process
+ public PathLiteralSingleParameter(object Path)
+ {
+ InputObject = Path;
+ string actualpath;
+ try { actualpath = LanguagePrimitives.ConvertTo(PathFileSystemParameterBase.GetObject(Path)); }
+ catch (Exception e) { throw new ArgumentException($"Failed to process {Path}! Error converting to string: {e.Message}", e); }
+
+ ApplyLiteral(actualpath, true, true);
+ }
+ #endregion Constructors
+
+ #region Operators
+ ///
+ /// Implicitly convert to string.
+ ///
+ /// The path to convert
+ public static implicit operator string(PathLiteralSingleParameter Path)
+ {
+ return Path.Path;
+ }
+
+ ///
+ /// Implicitly convert to DirectoryInfo.
+ ///
+ /// The path to convert
+ public static implicit operator DirectoryInfo(PathLiteralSingleParameter Path)
+ {
+ return new DirectoryInfo(Path.Path);
+ }
+
+ ///
+ /// Implicitly convert to DirectoryInfo.
+ ///
+ /// The path to convert
+ public static implicit operator FileInfo(PathLiteralSingleParameter Path)
+ {
+ return new FileInfo(Path.Path);
+ }
+
+ ///
+ /// Implicitly convert DirectoryInfo to PathDirectorySingleParameter.
+ ///
+ /// The path to convert
+ public static implicit operator PathLiteralSingleParameter(DirectoryInfo Info)
+ {
+ return new PathLiteralSingleParameter(Info);
+ }
+
+ ///
+ /// Implicitly convert FileInfo to PathDirectorySingleParameter.
+ ///
+ /// The path to convert
+ public static implicit operator PathLiteralSingleParameter(FileInfo Info)
+ {
+ return new PathLiteralSingleParameter(Info);
+ }
+
+ ///
+ /// Implicitly convert to FileSystemInfo.
+ ///
+ /// The path to convert
+ public static implicit operator FileSystemInfo(PathLiteralSingleParameter Path)
+ {
+ if (File.Exists(Path.Path))
+ return new FileInfo(Path.Path);
+ return new DirectoryInfo(Path.Path);
+ }
+ #endregion Operators
+ }
+}
diff --git a/library/PSFramework/Parameter/PathNewFileSingleParameter.cs b/library/PSFramework/Parameter/PathNewFileSingleParameter.cs
new file mode 100644
index 00000000..15a3149f
--- /dev/null
+++ b/library/PSFramework/Parameter/PathNewFileSingleParameter.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Parameter
+{
+ ///
+ /// ParameterClass to map to a single file that needs not exist, but whose parent directory must.
+ ///
+ public class PathNewFileSingleParameter : PathFileSystemSingleParameterBase
+ {
+ #region Constructors
+ ///
+ /// Processes a single string as a single file.
+ ///
+ /// The path to process
+ public PathNewFileSingleParameter(string Path)
+ {
+ InputObject = Path;
+ ApplyNew(Path);
+ }
+
+ ///
+ /// Processes a single FileInfo item as a single file.
+ ///
+ /// The path to process
+ public PathNewFileSingleParameter(FileInfo Path) : this(Path.FullName) { InputObject = Path; }
+
+ ///
+ /// Processes a single Uri as a single file.
+ ///
+ /// The path to process
+ public PathNewFileSingleParameter(Uri Path) : this(Path.OriginalString) { InputObject = Path; }
+
+ ///
+ /// Processes a single object as a single file.
+ ///
+ /// The path to process
+ public PathNewFileSingleParameter(object Path)
+ {
+ InputObject = Path;
+ string actualpath;
+ try { actualpath = LanguagePrimitives.ConvertTo(PathFileSystemParameterBase.GetObject(Path)); }
+ catch (Exception e) { throw new ArgumentException($"Failed to process {Path}! Error converting to string: {e.Message}", e); }
+
+ ApplyNew(actualpath);
+ }
+ #endregion Constructors
+
+ #region Operators
+ ///
+ /// Implicitly convert to string.
+ ///
+ /// The path to convert
+ public static implicit operator string(PathNewFileSingleParameter Path)
+ {
+ return Path.Path;
+ }
+
+ ///
+ /// Implicitly convert to FileInfo.
+ ///
+ /// The path to convert
+ public static implicit operator FileInfo(PathNewFileSingleParameter Path)
+ {
+ return new FileInfo(Path.Path);
+ }
+
+ ///
+ /// Implicitly convert FileInfo to PathDirectorySingleParameter.
+ ///
+ /// The path to convert
+ public static implicit operator PathNewFileSingleParameter(FileInfo Info)
+ {
+ return new PathNewFileSingleParameter(Info);
+ }
+
+ ///
+ /// Implicitly convert to FileSystemInfo.
+ ///
+ /// The path to convert
+ public static implicit operator FileSystemInfo(PathNewFileSingleParameter Path)
+ {
+ return new FileInfo(Path.Path);
+ }
+ #endregion Operators
+
+ internal void ApplyNew(string Path)
+ {
+ SessionState state = new SessionState();
+ string basePath = state.Path.GetUnresolvedProviderPathFromPSPath(Path);
+ string parentPath = state.Path.ParseParent(basePath, "");
+
+ if (Directory.Exists(basePath))
+ throw new ArgumentException($"Invalid input: Target path is a directory, not a file! {Path}");
+ if (!File.Exists(basePath) && !Directory.Exists(parentPath))
+ throw new ArgumentException($"Invalid input: Neither file nor parent folder exist! {Path}");
+
+ this.Path = basePath;
+ }
+ }
+}
diff --git a/library/PSFramework/TabExpansion/PsfArgumentCompleterAttribute.cs b/library/PSFramework/TabExpansion/PsfArgumentCompleterAttribute.cs
index a7602a4a..a83400b2 100644
--- a/library/PSFramework/TabExpansion/PsfArgumentCompleterAttribute.cs
+++ b/library/PSFramework/TabExpansion/PsfArgumentCompleterAttribute.cs
@@ -9,6 +9,11 @@ namespace PSFramework.TabExpansion
///
public class PsfArgumentCompleterAttribute : Attribute
{
+ ///
+ /// The name of the completion
+ ///
+ public string CompletionName = "";
+
///
/// Create an argument completer offering but the name of the registered completion
///
@@ -64,6 +69,11 @@ public PsfArgumentCompleterAttribute(ScriptBlock ScriptBlock, string Name, bool
///
public class PsfArgumentCompleterAttribute : ArgumentCompleterAttribute
{
+ ///
+ /// The name of the completion
+ ///
+ public string CompletionName = "";
+
///
/// Create an argument completer offering but the name of the registered completion
///
@@ -71,7 +81,7 @@ public class PsfArgumentCompleterAttribute : ArgumentCompleterAttribute
public PsfArgumentCompleterAttribute(string Completion)
: base(TabExpansionHost.Scripts.ContainsKey(Completion) ? TabExpansionHost.Scripts[Completion].ScriptBlock : ScriptBlock.Create(""))
{
-
+ CompletionName = Completion;
}
///
@@ -103,7 +113,7 @@ public PsfArgumentCompleterAttribute(ScriptBlock ScriptBlock, bool Global)
public PsfArgumentCompleterAttribute(ScriptBlock ScriptBlock, string Name)
: base(TabExpansionHost.RegisterCompletion(Name, ScriptBlock, TeppScriptMode.Auto, new Parameter.TimeSpanParameter(0), false, true).ScriptBlock)
{
-
+ CompletionName = Name;
}
///
@@ -115,7 +125,7 @@ public PsfArgumentCompleterAttribute(ScriptBlock ScriptBlock, string Name)
public PsfArgumentCompleterAttribute(ScriptBlock ScriptBlock, string Name, bool Global)
: base(TabExpansionHost.RegisterCompletion(Name, ScriptBlock, TeppScriptMode.Auto, new Parameter.TimeSpanParameter(0), Global, true).ScriptBlock)
{
-
+ CompletionName = Name;
}
}
#endif
diff --git a/library/PSFramework/TabExpansion/ScriptContainer.cs b/library/PSFramework/TabExpansion/ScriptContainer.cs
index af9dc89d..1e131cf5 100644
--- a/library/PSFramework/TabExpansion/ScriptContainer.cs
+++ b/library/PSFramework/TabExpansion/ScriptContainer.cs
@@ -1,9 +1,13 @@
using PSFramework.Extension;
using PSFramework.Utility;
using System;
+using System.Collections;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
+using System.Text;
+using System.Text.RegularExpressions;
namespace PSFramework.TabExpansion
{
@@ -62,6 +66,56 @@ public class ScriptContainer
///
public bool Global;
+ ///
+ /// If true: Match input against any part of the options, not just the beginning
+ ///
+ public bool MatchAnywhere
+ {
+ set { _MatchAnywhere = value; }
+ get
+ {
+ if (null != _MatchAnywhere)
+ return (bool)_MatchAnywhere;
+ return TabExpansionHost.MatchAnywhere;
+ }
+ }
+ private Nullable _MatchAnywhere;
+
+ ///
+ /// If true: Wrap all results into quotes, not just those with whitespace
+ ///
+ public bool AlwaysQuote
+ {
+ set { _AlwaysQuote = value; }
+ get
+ {
+ if (null != _AlwaysQuote)
+ return (bool)_AlwaysQuote;
+ return TabExpansionHost.AlwaysQuote;
+ }
+ }
+ private Nullable _AlwaysQuote;
+
+ ///
+ /// If true: Apply FuzzyMatching to the legal completions, not just direct word matching.
+ ///
+ public bool FuzzyMatch
+ {
+ set { _FuzzyMatch = value; }
+ get
+ {
+ if (null != _FuzzyMatch)
+ return (bool)_FuzzyMatch;
+ return TabExpansionHost.FuzzyMatch;
+ }
+ }
+ private Nullable _FuzzyMatch;
+
+ ///
+ /// If true: Completion results will not be sorted by name.
+ ///
+ public bool DontSort;
+
///
/// Returns whether a new refresh of tab completion should be executed.
///
@@ -129,11 +183,95 @@ public string[] Invoke()
ScriptBlock tempScriptBlock = ScriptBlock;
if (Global)
tempScriptBlock = ScriptBlock.Clone().ToGlobal();
-
+
foreach (PSObject item in tempScriptBlock.Invoke(arguments))
results.Add((string)item.Properties["CompletionText"].Value);
-
+
return results.ToArray();
}
+
+ ///
+ /// Returns the matching pattern applied against the list of completion values.
+ ///
+ /// What the user typed so far
+ /// The resolved pattern to match with
+ public string GetPattern(string WordToComplete)
+ {
+ StringBuilder stringBuilder = new StringBuilder();
+ if (!MatchAnywhere && !FuzzyMatch)
+ stringBuilder.Append("^['\"]{0,1}");
+
+ string escaped = Regex.Escape(WordToComplete.Trim("\"'".ToCharArray()));
+
+ if (!FuzzyMatch)
+ stringBuilder.Append(Regex.Escape(WordToComplete.Trim("\"'".ToCharArray())));
+ else
+ {
+ foreach (char character in WordToComplete.Trim("\"'".ToCharArray()).ToCharArray())
+ {
+ stringBuilder.Append(Regex.Escape(character.ToString()));
+ stringBuilder.Append(".*");
+ }
+ }
+
+ return stringBuilder.ToString();
+ }
+
+ #region Trainable
+ private ConcurrentDictionary _Trained = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase);
+ ///
+ /// The trained values for the current completion scriptblock.
+ ///
+ public Hashtable[] Trained { get => _Trained.Values.ToArray(); }
+
+ ///
+ /// Whether this completion should automatically be trained with values provided to parameters
+ ///
+ public bool AutoTraining;
+
+ ///
+ /// Add a completion option to the list of trained completions.
+ ///
+ /// The value to offer for completion
+ public void AddTraining(string Text)
+ {
+ Hashtable result = new Hashtable(StringComparer.InvariantCultureIgnoreCase);
+ result["Text"] = Text;
+ _Trained[Text] = result;
+ }
+ ///
+ /// Add a completion option to the list of trained completions.
+ ///
+ /// A hashtable with the completion data to add. Must at least contain a "Text" node.
+ /// An invalid hashtable, not containing a key named "Text" will cause an argument exception
+ public void AddTraining(Hashtable Data)
+ {
+ if (Data == null || !Data.ContainsKey("Text") || Data["Text"] == null)
+ throw new ArgumentException("Invalid Hashtable, does not contain the required 'Text' key!");
+ _Trained[Data["Text"].ToString()] = Data;
+ }
+
+ ///
+ /// Remove a previously provided completion value.
+ ///
+ /// The text value to no longer complete.
+ public void RemoveTraining(string Text)
+ {
+ Hashtable temp;
+ _Trained.TryRemove(Text, out temp);
+ }
+
+ ///
+ /// Remove a previously provided completion value.
+ ///
+ /// Hashtable containing the completion to no longer offer.
+ public void RemoveTraining(Hashtable Data)
+ {
+ if (Data == null || !Data.ContainsKey("Text") || Data["Text"] == null)
+ return;
+ Hashtable temp;
+ _Trained.TryRemove(Data["Text"].ToString(), out temp);
+ }
+ #endregion Trainable
}
}
diff --git a/library/PSFramework/TabExpansion/TabExpansionHost.cs b/library/PSFramework/TabExpansion/TabExpansionHost.cs
index 2b3b6871..efaa3a46 100644
--- a/library/PSFramework/TabExpansion/TabExpansionHost.cs
+++ b/library/PSFramework/TabExpansion/TabExpansionHost.cs
@@ -2,6 +2,7 @@
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Language;
using PSFramework.Parameter;
@@ -39,6 +40,23 @@ public static Hashtable Cache
private static readonly object _CacheLock = new object();
#endregion State information
+ #region Settings
+ ///
+ /// Whether PSFramework completion should by default match type text to anywhere in the included/valid options' text, not just from the beginning.
+ ///
+ public static bool MatchAnywhere;
+
+ ///
+ /// Whether PSFramework completion should always wrap quotes around the completion text, even if there is no whitespace.
+ ///
+ public static bool AlwaysQuote;
+
+ ///
+ /// Whether PSFramework completion should use fuzzy-matching when matching completion values with the already typed text.
+ ///
+ public static bool FuzzyMatch;
+ #endregion Settings
+
#region Public logic access
///
/// Registers a new completion scriptblock
@@ -71,7 +89,13 @@ public static void RegisterCompletion(string Name, ScriptBlock ScriptBlock, Tepp
script.ScriptBlock = ScriptBlock.Create(SimpleCompletionScript.Replace("", Name));
script.InnerScriptBlock = ScriptBlock;
}
+
+ ScriptContainer oldScript;
+ Scripts.TryGetValue(Name, out oldScript);
Scripts[Name] = script;
+ if (oldScript != null && oldScript.Trained.Length > 0)
+ foreach (Hashtable training in oldScript.Trained)
+ script.AddTraining(training);
}
///
diff --git a/library/PSFramework/Utility/ScriptTransformationAttribute.cs b/library/PSFramework/Utility/ScriptTransformationAttribute.cs
index bf33b6a7..4e3a2467 100644
--- a/library/PSFramework/Utility/ScriptTransformationAttribute.cs
+++ b/library/PSFramework/Utility/ScriptTransformationAttribute.cs
@@ -49,7 +49,7 @@ public override object Transform(EngineIntrinsics engineIntrinsics, object input
Collection result = Conversions[Name].InvokeGlobal(inputData);
if (result.Count() == 0 || result[0] == null)
throw new InvalidOperationException($"Conversion Script {Name} failed to return anything! Input: {inputData}");
- if (result[0].BaseObject.GetType() != TargetType)
+ if (result[0].BaseObject.GetType() != TargetType && !TargetType.IsAssignableFrom(result[0].BaseObject.GetType()))
throw new InvalidOperationException($"Conversion Script {Name} converted {inputData} to {result[0].BaseObject.GetType().FullName}, rather than {TargetType.FullName}");
return result[0];
}
diff --git a/library/PSFramework/Utility/UtilityHost.cs b/library/PSFramework/Utility/UtilityHost.cs
index 6da76cc2..ed0ebd6d 100644
--- a/library/PSFramework/Utility/UtilityHost.cs
+++ b/library/PSFramework/Utility/UtilityHost.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
+using System.Linq;
using System.Management.Automation;
using System.Net;
using System.Net.NetworkInformation;
@@ -264,6 +265,70 @@ public static string ExpandString(string CompressedString)
return result;
}
}
+
+ ///
+ /// Joins two path segments together. Will correct path separators, will trace back ".." path segments. Will ignore the Base, if the Child is an absolute Uri.
+ ///
+ /// The base path to which to add the child
+ /// The child path segement to add to the base
+ /// The fully joined path
+ public static string JoinPath(string Base, string Child)
+ {
+ string tempPath = Child;
+ Uri tempUri = new Uri(Child, UriKind.RelativeOrAbsolute);
+ if (!tempUri.IsAbsoluteUri)
+ tempPath = String.Join(Path.DirectorySeparatorChar.ToString(), new string[] { Base, Child });
+ bool isUnc = Regex.IsMatch(Base, "^\\\\|^//");
+
+ tempPath = Regex.Replace(tempPath, "[\\\\|/]+", Path.DirectorySeparatorChar.ToString());
+
+ string[] parts = tempPath.Split(Path.DirectorySeparatorChar);
+ do
+ {
+ bool doBreak = true;
+ for (int index = 0; index < parts.Length; index++)
+ {
+ if (parts[index] == ".")
+ {
+ parts = RemoveAt(parts, new int[] { index });
+ doBreak = false;
+ break;
+ }
+ if (parts[index] == "..")
+ {
+ parts = RemoveAt(parts, new int[] { index - 1, index });
+ doBreak = false;
+ break;
+ }
+ }
+ if (doBreak)
+ break;
+ }
+ while (true);
+
+ tempPath = String.Join(Path.DirectorySeparatorChar.ToString(), parts);
+ if (isUnc)
+ tempPath = Path.DirectorySeparatorChar.ToString() + tempPath;
+
+ return tempPath;
+ }
+
+ ///
+ /// Tool to remove items from an array at a specific location
+ ///
+ /// The array to remove items from
+ /// The indexes of the items to remove
+ /// A new array with all items of the source array, minus the ones at the specified indexes.
+ public static T[] RemoveAt(T[] Array, int[] Indexes)
+ {
+ List list = new List();
+ for (int i = 0; i < Array.Length; i++)
+ {
+ if (!Indexes.Contains(i))
+ list.Add(Array[i]);
+ }
+ return list.ToArray();
+ }
#endregion Strings
#region PowerShell Runtime
@@ -483,6 +548,54 @@ public static object InvokePrivateStaticMethod(Type StaticType, string Name, obj
return GetPrivateStaticMethod(StaticType, Name).Invoke(null, Arguments);
}
+ ///
+ /// Creates a new PSCustomObject based on the base object wrapped by the input.
+ ///
+ /// The item to refelct upon.
+ /// Whether to include public properties / fields
+ /// Whether to include private properties / fields
+ /// A new object containing only properties and fields of the base input. Anything else (including noteproperties of the input) will be ignored.
+ public static PSObject GetReflectedObject(PSObject Item, bool IncludePublic = true, bool IncludePrivate = true)
+ {
+ if (Item == null || Item.BaseObject == null)
+ return null;
+
+ PSObject temp = new PSObject();
+
+ Type type = Item.BaseObject.GetType();
+ if (IncludePublic)
+ {
+ foreach (PropertyInfo property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public))
+ {
+ PSNoteProperty newProperty = new PSNoteProperty(property.Name, property.GetValue(Item.BaseObject));
+ temp.Properties.Add(newProperty);
+ }
+
+ foreach (FieldInfo field in type.GetFields(BindingFlags.Instance | BindingFlags.Public))
+ {
+ PSNoteProperty newProperty = new PSNoteProperty(field.Name, field.GetValue(Item.BaseObject));
+ temp.Properties.Add(newProperty);
+ }
+ }
+
+ if (IncludePrivate)
+ {
+ foreach (PropertyInfo property in type.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic))
+ {
+ PSNoteProperty newProperty = new PSNoteProperty(property.Name, property.GetValue(Item.BaseObject));
+ temp.Properties.Add(newProperty);
+ }
+
+ foreach (FieldInfo field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic))
+ {
+ PSNoteProperty newProperty = new PSNoteProperty(field.Name, field.GetValue(Item.BaseObject));
+ temp.Properties.Add(newProperty);
+ }
+ }
+
+ return temp;
+ }
+
///
/// Execute a constructor for the specified type, creating a new instance of it.
/// It will try each constructor with the correct number of arguments until it finds a working one, so ... not REALLY efficient.