4

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.

5
  • 1
    I'm not sure what you mean with "easier" but you could use the according methods instead of the operators lie this: $String.Trim('@()').Split(',') Commented Dec 26, 2021 at 12:45
  • 3
    Extremely naughty Invoke-Expression, use at your own risk version without trimming - ($String | iex).Split(',') [Eyes] Commented Dec 26, 2021 at 13:10
  • Can the values contain embedded " and ,? Commented Dec 26, 2021 at 13:28
  • 4
    @Ash $String | iex is enough. Split is not needed here as the result is already the array. Commented Dec 26, 2021 at 13:30
  • 1
    @SagePourpre Doh, of course. Haha. Not fully with it today. Commented Dec 26, 2021 at 13:48

1 Answer 1

8

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.