不使用Invoke-Expression

时间:2015-07-14 22:01:05

标签: regex powershell

想象一下以下代码:

# Script Start
$WelcomeMessage = "Hello $UserName, today is $($Date.DayOfWeek)"

..
..
# 100 lines of other functions and what not...
..

function Greet-User
{
    $Username = Get-UserNameFromSomewhereFancy
    $Date = Get-DateFromSomewhereFancy

    $WelcomeMessage
}

这是一个非常基本的示例,但它试图显示的是一个脚本,其中有$WelcomeMessage运行脚本的人可以设置在脚本的顶部并控制消息显示的方式/内容是

第一件事是第一件事:为什么要做这样的事情?好吧,如果您将脚本传递给多个人,他们可能需要不同的消息。也许他们不喜欢$($Date.DayOfWeek)并希望得到完整的约会。也许他们不想显示用户名,无论如何。

其次,为什么要把它放在脚本的顶部?简单。如果您的脚本中有1000行,并且这些消息遍布整个脚本,则会使人们发现并更改这些消息成为一场噩梦。我们已经以局部字符串和东西的形式对静态消息执行此操作,因此除了其中的可变部分外,这不是什么新鲜事。

所以,现在问题。如果您运行该代码并调用Greet-User(假设用于检索用户名和日期的函数/ cmdlet实际存在并返回正确的内容......)Greet-User将始终返回Hello , today is

这是因为当您声明字符串时,在脚本顶部,当$UserName$Date对象都没有值时,会扩展字符串。

一个潜在的解决方法是使用单引号创建字符串,并使用Invoke-Expression展开它们。但由于空间的原因,这有点混乱。即:

$WelcomeMessage = 'Hello $env:USERNAME'
Invoke-Expression $WelcomeMessage

由于空间的原因会抛出错误,为了使其正常工作,必须将其声明为:

$WelcomeMessage = 'Hello $env:USERNAME'
$InvokeExpression = "`"$WelcomeMessage`""

凌乱...

此外,代码注入形式还存在另一个问题。由于我们允许用户在没有指定边界的情况下编写自己的欢迎消息,因此阻止他们输入类似的内容...

$WelcomeMessage 'Hello $([void] (Remove-Item C:\Windows -Force -Recurse))'

(是的,我知道这不会删除所有内容,但这只是一个例子)

当然这是一个脚本,如果他们可以修改该字符串,他们也可以修改脚本上的其他所有内容,但是我给出的例子是恶意利用脚本性质的人,也可能发生了某人意外在字符串中放入一些最终会产生不良后果的东西。

所以......如果没有使用Invoke-Expression,必须有一个更好的方法,我不能完全理解这一点,所以我们将不胜感激:)

3 个答案:

答案 0 :(得分:4)

将变量嵌入字符串不是创建动态文本的唯一方法,我这样做的方式是这样的:

$WelcomeMessage = 'Hello {0}, today is {1}'

# 100 lines of other functions and what not...

function Greet-User
{
    $Username = Get-UserNameFromSomewhereFancy
    $Date = Get-DateFromSomewhereFancy

    $WelcomeMessage -f $Username, $Date
}

答案 1 :(得分:3)

延迟评估字符串中表达式/变量的规范方法是将它们定义为单引号字符串,稍后再使用$ExecutionContext.InvokeCommand.ExpandString()

演示:

PS C:\> $s = '$env:COMPUTERNAME'
PS C:\> $s
$env:COMPUTERNAME
PS C:\> $ExecutionContext.InvokeCommand.ExpandString($s)
FOO

应用于您的示例代码:

$WelcomeMessage = 'Hello $UserName, today is $($Date.DayOfWeek)'

...
...
...

function Greet-User {
  $Username = Get-UserNameFromSomewhereFancy
  $Date = Get-DateFromSomewhereFancy

  $ExecutionContext.InvokeCommand.ExpandString($WelcomeMessage)
}

答案 2 :(得分:2)

你考虑过使用lambda表达式吗?即不是将变量定义为字符串值而是将其定义为函数,然后调用该函数在运行时传递相关参数。

$WelcomeMessage = {param($UserName,$Date);"Hello $UserName, today is $($Date.DayOfWeek) $([void](remove-item c:\test\test.txt))"}

#...
# 100 lines of other functions and what not...
#...

"testfile" >> c:\test\test.txt #ensure we have a test file to be deleted

function Get-UserNameFromSomewhereFancy(){return "myUsername";}
function Get-DateFromSomewhereFancy(){return (get-date);}

function Greet-User
{
    $Username = Get-UserNameFromSomewhereFancy
    $Date = Get-DateFromSomewhereFancy

    $WelcomeMessage.invoke($username,$date)
}

cls
Greet-User

<强>更新

如果你只想允许变量替换,下面的代码可以解决问题;但这无法完成更高级的功能(例如.DayOfWeek

$WelcomeMessage = 'Hello $Username, today is $($Date.DayOfWeek) $([void](remove-item c:\test\test.txt))'
#...
# 100 lines of other functions and what not...
#...

"testfile" >> c:\test\test.txt #ensure we have a test file to be deleted

function Get-UserNameFromSomewhereFancy(){return "myUsername";}
function Get-DateFromSomewhereFancy(){return (get-date);}
function Resolve-WelcomeMessage(){
    write-output {param($UserName,$Date);"$WelcomeMessage";}
}
function Greet-User
{
    $Username = Get-UserNameFromSomewhereFancy
    $Date = Get-DateFromSomewhereFancy
    $temp = $WelcomeMessage 
    get-variable | ?{@('$','?','^') -notcontains $_.Name} | sort name -Descending | %{
        $temp  = $temp -replace ("\`${0}" -f $_.name),$_.value
    }
    $temp 
}

cls
Greet-User

<强>更新

为了避免代码注入,这会使用-whatif;这只会对注入的代码支持whatif功能的地方有所帮助,但希望总比没有好......

此外,代码现在不需要声明参数;但只需要获取执行时可用的变量。

$WelcomeMessage = {"Hello $Username, today is $($Date.DayOfWeek) $([void](remove-item c:\test\test.txt))"}

#...
# 100 lines of other functions and what not...
#...

function Get-UserNameFromSomewhereFancy(){return "myUsername";}
function Get-DateFromSomewhereFancy(){return (get-date);}
function Resolve-WelcomeMessage(){
    write-output {param($UserName,$Date);"$WelcomeMessage";}
}

"testfile" >> c:\test\test.txt #ensure we have a test file to be deleted

function Greet-User {
    [cmdletbinding(SupportsShouldProcess=$True)]
    param()
    begin {$original = $WhatIfPreference; $WhatIfPreference = $true;}
    process {
        $Username = Get-UserNameFromSomewhereFancy
        $Date = Get-DateFromSomewhereFancy
        & $WelcomeMessage 
    }
    end {$WhatIfPreference = $original;}
}

cls
Greet-User