我正在重构我工作中需要的巨大脚本,并且我想在一些需要加载各种CSV文件的实用程序函数中使用splatting。我的问题是获取数据文件的目标。我之前使用过REF参数,但是当我这样做时需要更多的开销,因为我不能在哈希表中使用嵌套对象作为REF,所以它必须包含在一个函数中,而且当它处理时很麻烦处理这么多文件。我可以使用列出最终元素名称的属性然后硬编码变量的开头......但这似乎不是一个优雅的解决方案。
self.charset
这是我脚本的超缩略摘录:
$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;
当我在&#34; Debug5&#34;之后注释掉该行时,我收到此错误:
[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
我使用此博客作为灵感(步骤11):Powershell: Dynamically Creating Variable Names; Nested Hashtables
答案 0 :(得分:2)
我建议的一件事就是摆脱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
这是正确的。