首页 > 解决方案 > 用于变量名的 PowerShell 调用表达式

问题描述

我正在重构我在工作中需要的一个巨大的脚本,我想在一些需要加载各种 CSV 文件的实用程序函数中使用 splatting。我的问题是获取数据文件的目标。我以前使用过 REF 参数,但是当我这样做时,它需要更多的开销,因为我不能使用哈希表中的嵌套对象作为 REF,所以它必须包装在一个函数中,这在处理这么多文件时很麻烦。我可以使用列出最终元素名称的属性,然后硬编码变量的开头......但这似乎不是一个优雅的解决方案。

$m.DataFiles.('LDAP').HashArray = ...

这是我的脚本的超级缩写摘录:

################################################################################
#############################   SCRIPT SETTINGS    #############################
################################################################################
Set-StrictMode -Version latest;
$Global:ErrorActionPreference = 'Stop'; # Options: 'SilentlyContinue','Continue','Stop','Inquire'
$m = @{
    DataFiles   = @{
        LDAP        = @{
            FilePath        = 'C:\Temp\_PPtoO365_LDAP.csv';     ## Full path and file name: 'C:\Temp\log.csv';
            Label           = 'LdapFile';                       ## If present, then I/O functions are written to log;
            Append          = $False;                           ## Should append vs re-write the file;
            Fatal           = $True;                            ## Should a read/write failure cause immediate script exit;
            Dirty           = $False;                           ## Used internally as a save flag;
            Target          = "`$m.DataFiles.LDAP.HashArray";   ## Name of variable for Load-HashArray();
            DelimeterCSV    = ',';                              ## CSV [column] delimeter;
            DelimeterArray  = ';';                              ## Array delimeter (within a [column]);
            HashArray       = @();                              ## Array of HashTable(s);
            Template        = [Ordered]@{                       ## HashTable template for loading/saving;
                SamAccountName              = '';
                MailNickname                = '';
                Mail                        = '';
                Primaries                   = @();
                Aliases                     = @();
            };
        };
    };
    Settings    = @{
        SaveExamples    = $False;
        SaveDebug       = $False;
    };
};    

################################################################################
##############################        MAIN        ##############################
################################################################################

Function Do-Main() {
    Write-Output ([string]::Format("[{0}] 'LDAP' records; Before.",$m.DataFiles.LDAP.HashArray.Length.ToString('#,##0')));
    $params = $m.DataFiles.LDAP;
    Load-HashArray1 @params;
    Write-Output ([string]::Format("[{0}] 'LDAP' records; After Load-HashArray1().",$m.DataFiles.LDAP.HashArray.Length.ToString('#,##0')));
    $m.DataFiles.LDAP.HashArray = @(); ## Reset
    Write-Output ([string]::Format("[{0}] 'LDAP' records; After Reset.",$m.DataFiles.LDAP.HashArray.Length.ToString('#,##0')));
    Load-HashArray2 @params;
    Write-Output ([string]::Format("[{0}] 'LDAP' records; After Load-HashArray2().",$m.DataFiles.LDAP.HashArray.Length.ToString('#,##0')));
    Set-Close;
    Return;
}

Function Set-Close() {
    ## Script Cleanup - reduce HEAP ##
    $m.Clear();
    Exit(0);
}

################################################################################
##############################     Functions      ##############################
################################################################################


################################################################################
############################## Utility Functions  ##############################
################################################################################

##### Function: Loads a CSV file #####
## Will return one of four types of collections:
##  1. If Target is @(), and no Template: Object[PSCustomObject] (default)
##  2. If Target is @(), and    Template: Object[HashTable] 
##  3. If Target is @{}, and no Template: HashTable [Ordered]@{index=PSCustomObject}
##  4. If Target is @{}, and    Template: HashTable [Ordered]@{index=HashTable}
Function Load-HashArray1() {
    Param (
        [parameter(Mandatory=$True)][String]                                            $FilePath,                  ## Full path and file name: 'C:\Temp\log.csv';
        [parameter(Mandatory=$True)][String]                                            $Label,                     ## If present, then I/O functions are written to log;
        [parameter(Mandatory=$False)][Switch]                                           $Append         = $False,   ## Should append vs re-write the file;
        [parameter(Mandatory=$False)][Switch]                                           $Fatal          = $False,   ## Should a read/write failure cause immediate script exit;
        [parameter(Mandatory=$False)][Switch]                                           $Dirty          = $False,   ## Used internally as a save flag;
        [parameter(Mandatory=$True)][String]                                            $Target,                    ## Name of variable to hold the HashArray;
        [parameter(Mandatory=$False)][String]                                           $DelimeterCSV   = ',',      ## CSV [column] delimeter;
        [parameter(Mandatory=$False)][String]                                           $DelimeterArray = ';',      ## Array delimeter (within a [column]);
        [parameter(Mandatory=$False)][Object[]]                                         $HashArray      = @(),      ## Not used in this function;
        [parameter(Mandatory=$False)][System.Collections.Specialized.OrderedDictionary] $Template       = @{}       ## HashTable template for loading/saving;
    )
Write-Output "<<Debug1>> `$Target [$Target]... Expecting [`$m.DataFiles.LDAP.HashArray]";
    $f = @{
        Data        = @($Template,$Template,$Template);
        Target      = $m.DataFiles.LDAP.HashArray;
        TargetType  = '';
    };
Write-Output "<<Debug2>> `$f.Target [$($f.Target)]... Expecting []";
    $f.TargetType   = $f.Target.GetType().Name;
Write-Output "<<Debug3>> `$f.TargetType [$($f.TargetType)]... Expecting [Object[]]";
    $Null = ($m.DataFiles.LDAP.HashArray = $f.Data);
    Return;
}

Function Load-HashArray2() {
    Param (
        [parameter(Mandatory=$True)][String]                                            $FilePath,                  ## Full path and file name: 'C:\Temp\log.csv';
        [parameter(Mandatory=$True)][String]                                            $Label,                     ## If present, then I/O functions are written to log;
        [parameter(Mandatory=$False)][Switch]                                           $Append         = $False,   ## Should append vs re-write the file;
        [parameter(Mandatory=$False)][Switch]                                           $Fatal          = $False,   ## Should a read/write failure cause immediate script exit;
        [parameter(Mandatory=$False)][Switch]                                           $Dirty          = $False,   ## Used internally as a save flag;
        [parameter(Mandatory=$True)][String]                                            $Target,                    ## Name of variable to hold the HashArray;
        [parameter(Mandatory=$False)][String]                                           $DelimeterCSV   = ',',      ## CSV [column] delimeter;
        [parameter(Mandatory=$False)][String]                                           $DelimeterArray = ';',      ## Array delimeter (within a [column]);
        [parameter(Mandatory=$False)][Object[]]                                         $HashArray      = @(),      ## Not used in this function;
        [parameter(Mandatory=$False)][System.Collections.Specialized.OrderedDictionary] $Template       = @{}       ## HashTable template for loading/saving;
    )
Write-Output "<<Debug4>> `$Target [$Target]... Expecting [`$m.DataFiles.LDAP.HashArray]";
    $f = @{
        Data        = @($Template,$Template,$Template);
        Target      = Invoke-Expression ($Target);
        TargetType  = '';
    };
Write-Output "<<Debug5>> `$f.Target [$($f.Target)]... Expecting []";
    $f.TargetType   = $f.Target.GetType().Name;
Write-Output "<<Debug6>> `$f.TargetType [$($f.TargetType)]... Expecting [Object[] or HashTable]";
    $Null = Invoke-Expression ($Target = $f.Data);
    #$f.Target = $f.Data;
    Return;
}

################################################################################
##############################    Script Entry    ##############################
################################################################################
Do-Main;

执行时返回:

[0] 'LDAP' records; Before.
<<Debug1>> $Target [$m.DataFiles.LDAP.HashArray]... Expecting [$m.DataFiles.LDAP.HashArray]
<<Debug2>> $f.Target []... Expecting []
<<Debug3>> $f.TargetType [Object[]]... Expecting [Object[]]
[3] 'LDAP' records; After Load-HashArray1().
[0] 'LDAP' records; After Reset.
<<Debug4>> $Target [$m.DataFiles.LDAP.HashArray]... Expecting [$m.DataFiles.LDAP.HashArray]
<<Debug5>> $f.Target []... Expecting []
Load-HashArray2 : You cannot call a method on a null-valued expression.
At C:\Temp\__Test.ps1:44 char:9
+         Load-HashArray2 @params;
+         ~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [Load-HashArray2], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull,Load-HashArray2

当我在“Debug5”之后注释掉该行时,我收到此错误:

<<Debug5>> $f.Target []... Expecting []
<<Debug6>> $f.TargetType []... Expecting [Object[] or HashTable]
Invoke-Expression : The term 'System.Collections.Specialized.OrderedDictionary' is not recognized as the name of a
cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify
that the path is correct and try again.
At C:\Temp\__Test.ps1:119 char:17
+         $Null = Invoke-Expression ($Target = $f.Data);
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (System.Collecti...deredDictionary:String) [Invoke-Expression], CommandN
   otFoundException
    + FullyQualifiedErrorId : CommandNotFoundException,Microsoft.PowerShell.Commands.InvokeExpressionCommand

我使用这个博客作为灵感(第 11 步):Powershell:动态创建变量名称;嵌套哈希表

标签: powershell

解决方案


我建议的一件事是摆脱Invoke-Expression并使用带有呼叫&运算符的脚本块。所以这

Target          = "`$m.DataFiles.LDAP.HashArray"

变成这个

Target          = { $m.DataFiles.LDAP.HashArray } # no quoting issues

并在调用时,

Target      = Invoke-Expression ($Target)

简单地变成

Target  = & $Target

您在此处收到错误的原因

$Null = Invoke-Expression ($Target = $f.Data)

是因为$f.Data是有序字典。Invoke-Expression计算ToString()其参数的 并且ToString()集合的 是集合类型的名称。摆脱Invoke-Expression应该使调试代码更容易。(注意:一般来说,使用Invoke-Expression几乎总是错误的做法,并且可能存在安全隐患;请参阅Invoke-Expression Considered Harmful

加上一些小评论:

首先,这

$Null = ($m.DataFiles.LDAP.HashArray = $f.Data);

应该只是

$m.DataFiles.LDAP.HashArray = $f.Data

因为作为语句的赋值不返回值。

其次,在调用命令时不应使用括号,如

Target      = Invoke-Expression ($Target);

因为它会导致人们认为他们需要做Copy-Item("from", "to")的是错误的而不是Copy-Item from to正确的。


推荐阅读