简体   繁体   中英

PowerShell Invoke-Expression for variable name

I'm refactoring a HUGE script I need at work and I want to use splatting in a few utility functions that need to load various CSV files. My problem is getting the target for the datafile. I had previously used REF parameters, but when I do that it takes more overhead because I can't use a nested object inside a hashtable as the REF so it must be wrapped in a function and that's just messy when dealing with this many files. I could use a property listing the name of the final element and then hardcode the beginning of the variable... but that does not seem like an elegant solution.

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

Here is a super abbreviated extract of my script:

################################################################################
#############################   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;

When executed it returns:

[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

When I comment out the line right after "Debug5", I get this error:

<<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

I'm using this blog as inspiration (step 11): Powershell: Dynamically Creating Variable Names; Nested Hashtables

One thing I would suggest off the bat is to get rid of Invoke-Expression and use scriptblocks with the call & operator instead. So this

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

becomes this

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

and on invocation,

Target      = Invoke-Expression ($Target)

simply becomes

Target  = & $Target

The reason you're getting the error here

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

is because $f.Data is an ordered dictionary. Invoke-Expression evaluates the ToString() of its arguments and the ToString() of the collection is the name of the collections type. Getting rid of Invoke-Expression should make it easier to debug your code. (NOTE: in general, using Invoke-Expression is almost always the wrong thing to do and has possible security implications; see Invoke-Expression Considered Harmful )

Plus some minor comments:

First, this

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

should simply be

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

because assignments as statements don't return values.

Second, you shouldn't use parentheses when invoking commands as in

Target      = Invoke-Expression ($Target);

because it can lead to people into thinking that they need to do Copy-Item("from", "to") which is wrong instead of Copy-Item from to which is correct.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM