Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Powershell reevaluate string to array

i have a number of strings like this this that i would like powershell to reevaluate\convert to an array (like what would happen if you just wrote the same code in ISE without the single quotes).

$String = '@("a value","b value","c value")'

Is there a easier way to do this than stripping the '@' & '()' out of the string and using -split?

Thanks for the help in advance.

like image 845
Pete Avatar asked Oct 15 '25 00:10

Pete


1 Answers

As long as the string contains a valid expression, you can use the [scriptblock]::Create(..) method:

$String = '@("a value","b value","c value")'
& ([scriptblock]::Create($String))

Invoke-Expression would also work and in this case would be mostly the same thing.


However, the nice feature about Script Blocks, as zett42 pointed out in a comment, is that with them we can validate that arbitrary code execution is forbidden with it's CheckRestrictedLanguage method.

In example below, Write-Host is an allowed command and a string containing only 'Write-Host "Hello world!"' would not throw an exception however, assignment statements or any other command not listed in $allowedCommands will throw and the script block will not be executed.

$String = @'
Write-Host "Hello world!"
$stream = [System.Net.Sockets.TcpClient]::new('google.com', 80).GetStream()
'@

[string[]] $allowedCommands  =  'Write-Host'
[string[]] $allowedVaribales =  ''

try {
    $scriptblock = [scriptblock]::Create($String)
    $scriptblock.CheckRestrictedLanguage(
        $allowedCommands,
        $allowedVaribales,
        $false) # Argument to allow Environmental Variables
    & $scriptblock
}
catch {
    Write-Warning $_.Exception.Message
}

Another alternative is to run the expression in a Runspace with ConstrainedLanguage Mode. This function can make it really easy.

using namespace System.Management.Automation.Language
using namespace System.Management.Automation.Runspaces
using namespace System.Management.Automation
using namespace System.Collections.ObjectModel

function Invoke-ConstrainedExpression {
    [OutputType([PSDataStreams])]
    [CmdletBinding(DefaultParameterSetName = 'ScriptBlock')]
    param(
        [Parameter(
            ParameterSetName = 'Command',
            Mandatory,
            ValueFromPipeline,
            Position = 0)]
        [string] $Command,

        [Parameter(
            ParameterSetName = 'ScriptBlock',
            Mandatory,
            Position = 0)]
        [scriptblock] $ScriptBlock,

        [Parameter()]
        [PSLanguageMode] $LanguageMode = 'ConstrainedLanguage',

        # When using this switch, the function inspects the AST to find any variable
        # not being an assigned one in the expression, queries the local state to find
        # it's value and injects that variable to the Initial Session State of the Runspace.
        [Parameter()]
        [switch] $InjectLocalVariables
    )

    process {
        try {
            $Expression = $ScriptBlock
            if ($PSBoundParameters.ContainsKey('Command')) {
                $Expression = [scriptblock]::Create($Command)
            }

            # bare minimum for the session state
            $iss = [initialsessionstate]::CreateDefault2()
            # set `ContrainedLanguage` for this session
            $iss.LanguageMode = $LanguageMode

            if ($InjectLocalVariables.IsPresent) {
                $ast = $Expression.Ast
                $map = [HashSet[string]]::new([StringComparer]::InvariantCultureIgnoreCase)
                $ast.FindAll(
                    { $args[0] -is [AssignmentStatementAst] }, $true).Left.Extent.Text |
                    ForEach-Object { $null = $map.Add($_) }

                $variablesToInject = $ast.FindAll(
                    { $args[0] -is [VariableExpressionAst] -and -not $map.Contains($args[0].Extent.Text) }, $true).
                VariablePath.UserPath

                foreach ($var in $variablesToInject) {
                    $value = $PSCmdlet.GetVariableValue($var)
                    $entry = [SessionStateVariableEntry]::new($var, $value, '')
                    $iss.Variables.Add($entry)
                }
            }

            # create the PS Instance and add the expression to invoke
            $ps = [powershell]::Create($iss).AddScript($Expression, $true)
            # invoke the expression
            $stdout = $ps.Invoke()
        }
        catch {
            $ps.Streams.Error.Add($_)
        }
        finally {
            if (-not $stdout) {
                $stdout = [Collection[psobject]]::new()
            }
            $ps.Streams.PSObject.Properties.Add([psnoteproperty]::new('Success', $stdout))
        }

        $ps.Streams
    }
}

Now we can test the expression using Constrained Language:

Invoke-ConstrainedExpression {
    Write-Host 'Starting script'
    [System.Net.WebClient]::new().DownloadString($uri) | iex
    'Hello world!'
}

The output would look like this:

Success     : {Hello world!}
Error       : {Cannot create type. Only core types are supported in this language mode.}
Progress    : {}
Verbose     : {}
Debug       : {}
Warning     : {}
Information : {Starting script}

DevBlogs Article: PowerShell Constrained Language Mode has some nice information and is definitely worth a read.

like image 54
Santiago Squarzon Avatar answered Oct 17 '25 15:10

Santiago Squarzon