Why is $args array empty when this function is called? - arrays

I found an error in how the following code works, excerpted from MSFT_Archive.psm1, the Archive DSC resource.
Function Get-CacheEntry
{
$key = [string]::Join($args).GetHashCode().ToString()
Trace-Message "Using ($key) to retrieve hash value"
$path = Join-Path $CacheLocation $key
if(-not (Test-Path $path))
{
Trace-Message "No cache value found"
return #{}
}
else
{
$tmp = Import-CliXml $path
Trace-Message "Cache value found, returning $tmp"
return $tmp
}
}
The line "$key = [string]::Join($args).GetHashCode().ToString()" does the wrong thing because $args always comes out as being an empty array. A typical call to this method is:
$cacheObj = Get-CacheEntry $Path $Destination
I added print statements and $Path and $Destination have values; they are not empty or nil. Because the $args array is empty, the value of $key is always the same, and consequently all cache files get the same name, regardless of the Zip archive being unpacked. Different inputs then lead to the same cache file being consulted, leading to the same file being repeatedly unpacked even when nothing changes.
The method has no named parameters, so $args should always have the list of unbound parameters. What is wrong?
I am using Powershell 4.0 on a Windows Server 2008R2 system with DSC Resource Kit Wave 10.
UPDATE: This problem is present in both Archive and xArchive resources.

A function with only [string]::Join($args) will produce empty results as you have seen. As EBGreen explains you need to have at least 2 arguments in the method (depending which overload you use). As an alternative you can try to use the -join operator instead.
$key = (-join $args).GetHashCode().ToString()

[string]::Join() does not have a single argument overload. Try this:
$key = [string]::Join('', $args).GetHashCode().ToString()

Related

How do I access properties of nested arrays in powershell within foreach loops?

I'm trying to write a script that checks whether a group of registry keys are set individually and then either sets them or modifies them conditional on thier current state.
The keys that I want to check/set contain a mix of Strings and DWORDs
I want to loop through an array that contains the key I want to set paired with the value.
I've tried as a hashtable/splatting but the input to Get-ItemProperty fails because of the value parameter so I tried basic arrays instead with no luck.
They are all at the same registry path but I was attempting to do something similar to this:
$Path = "HKLM:\Software\path\to\keys"
$Properties = (
('key', value),
('key2', value2),
('key3', 'value3')
)
foreach ($item in $Properties){
$exist = Get-ItemProperty -Path $Path -name $item[0]
if ($exist) {
Set-ItemProperty -Path $Path -Name $item[0] -Value $item[1]
} else {
New-ItemProperty -Path $Path -Name $item[0] -Value $item[1]
}
}
But no matter what I've tried I cannot retrieve the individual elements of the inner arrays.
I realize I could probably do this long-form and just do it line by line rather than attempting to iterate through, but this is definitely a more elegant way, and would be a great template if I need to do something similar in the future.
Holy moly. Nothing breaks you out of a rut like posting the question to an online forum.
The .GetValue() method is what I needed, not the raw index number.
...So $Item.GetValue(0) for the key name and $Item.GetValue(1) for the value.
Feel silly answering my own question but hopefully it helps someone else!

Possible to have different array names when write-output PSCustomObject]#{ in my function?

Is there any way to name the variable for an array something in my Catch { and then have another array name for the array inside Try/script part of my function?
Cause when i try doing like this $computerObject = [PSCustomObject]#{
and then doing Write-Output $computerArray i can only get either my variables inside Try/script array being displayed inside Powershell window. Or only get the $error message from my Catch.. Is there any way to name each array something so i can do like below.
write-host "Results"
Write-Output $computerArray - display my first array here
write-host "Failed: computerlist" -foregroundcolor red
Write-Output $computerArray2 - display $error computers here. $error should just include computers who did not answer to ping and other stuff from my invoke-command computerlist.txt
The only true answer to why i need this separately is that sometimes i want my array in a CSV file. And sometimes i just want to copy info directly from Powershell window. And then its more practical to have failed computers separated and not in the same array output.
This function (as mentioned in comments) doesn't leverage the CIM cmdlets parallel capabilities, would recommend some tweaks to it but to answer the actual question, how can you "split" the output between success and fail:
The function as-is, doesn't require any modification to achieve this, it's try and catch blocks are outputting objects with the same properties and luckily one of those properties is Error and it's value is a boolean so you can simply first query all the computers and then split the result using .Where with Split mode.
The code would be like this:
$computers = 'computer1', 'computer2', ....
$computerArray = foreach($computer in $computers) {
Get-ComputerInformation -ComputerName $computer
}
# now we can split between FAIL and SUCCESS
$fail, $success = $computerArray.Where({ $_.Error }, 'Split')
$success | Export-Csv path\to\success.csv -NoTypeInformation
$fail | Export-Csv path\to\fail.csv -NoTypeInformation

Attempting to use two arrays as variables to search file names (powershell)

I need to search multiple (named) folders on multiple servers for files matching a specific date and copy those files to a local folder using Powershell. The number of folders are not the same size arrays as the number of servers. I.e. I need \server1\interfacefolders\folder1, \server1\interfacefolders\folder2, \server2\interfacefolders\folder1, \server2\interfacefolders\folder2, etc.
I have the following set up as arrays/variables preparing for this, I thought "nested" foreach loops would work, but it bombs out...any ideas how to get started on this?:
[string[]]$ProdServerArray = "server1", "server2", "server3"
[string[]]$InterfaceArray = "folder1", "folder2" "folder3" do {
$date = Read-host "Enter date (MM/DD/YYYY) : " } while ($date -as [datetime] -isnot [datetime])
$date = $date -as [datetime]
$destination = new-item c:\GetFilesResults\$($date.toshortdatestring().replace("/","-")) -type directory
$path = foreach ($ProdServer in $ProdServerArray)
{
$folder = foreach ($Interface in $InterfaceArray)
{
$file = "\\$path\InterfaceFolder\$folder\*"
if ("$file".LastWriteTime -gt $date.date)
{
Copy-Item -Path $file.fullname -Destination $destination
}
}
}
to build the full folder names from those two arrays, you can use two nested foreach loops. once you have the values, you can build the paths via something like the -f string format operator.
i left out the rest of your code since it does not appear to pertain to the question you asked. [grin]
$ProdServerArray = 'serverAAA', 'serverBbBbBb', 'server_CCC'
$InterfaceArray = 'folder1', 'folder2', 'folder3', 'folder666'
foreach ($PSA_Item in $ProdServerArray)
{
foreach ($IA_Item in $InterfaceArray)
{
'\\{0}\InterfaceFolders\{1}' -f $PSA_Item, $IA_Item
}
'=' * 30
}
output ...
\\serverAAA\InterfaceFolders\folder1
\\serverAAA\InterfaceFolders\folder2
\\serverAAA\InterfaceFolders\folder3
\\serverAAA\InterfaceFolders\folder666
==============================
\\serverBbBbBb\InterfaceFolders\folder1
\\serverBbBbBb\InterfaceFolders\folder2
\\serverBbBbBb\InterfaceFolders\folder3
\\serverBbBbBb\InterfaceFolders\folder666
==============================
\\server_CCC\InterfaceFolders\folder1
\\server_CCC\InterfaceFolders\folder2
\\server_CCC\InterfaceFolders\folder3
\\server_CCC\InterfaceFolders\folder666
==============================
First of all, you are missing a comma in the line:
[string[]]$InterfaceArray = "folder1", "folder2" "folder3"
Additionally, as far as I can tell, your do while loop doesn't appear to be accomplishing anything, as the only time this will ever be true is if the time 12:00:00AM exactly on the date specified. No matter what date you input in the format (MM/DD/YYYY), they will not be equal unless the case I said above.
Since you are searching multiple servers, Invoke-Command is your friend, as a foreach loop will act in series, while this will work in parallel. It will send out the search command to each server simultaneously.
I am not quite sure exactly what you are trying to do, so I did not fill in the actual search code(seen below), but the part i have left blank would be where you enter what filename/filename schema you are looking for. If you provide more clarity I can assist further if needed.
(Note: $filepath, although self explanatory, is the file paths you wish to search. You can generate them in a way similar to the one provided by Lee_Dailey. I'd recommend removing the divider lines and saving the paths generated to a String System.Array Object)
Invoke-Command -ComputerName $ProdServerArray -ScriptBlock {Get-Childitem –Path $filepath -Recurse -ErrorAction SilentlyContinue |where {<your code here>}}

Powershell - Multidimensional array issues

I've got some fun issues with a multidimensional array that just aren't making sense to me. I'm sure I'm just misusing something or expecting functionality that isn't quite there with PS. Here's what I'm trying to do:
$Packages = #()
$Packages += #("program 1","c:\path\to\program.exe","/switch")
$Packages += #("program 2","c:\path\to\program2.exe","/switch")
foreach ($Package in $Packages) {
$Name, $Path, $Args = $Package
Install-Program $Name $Path $Args
}
Install-Program is a function I've written that checks the package filetype (currently works with .msp, .msu, .msi, and .exe) and just has it install as silently as I can manage it (obviously not all .exe installers have a silent switch, grumble grumble).
The issue seems to be that when I iterate through the $Packages array, the $Package entry it pulls is from every element in each row of the array, i.e., it returns
$Package=$Packages[0][0] then $Package=$Packages[0][1] etc.,
instead of
$Package=$Packages[0] then $Package=$Packages[1] etc.
What's the least complicated way to make it do the latter?

Why can't I lookup an array index inside a foreach loop in Powershell?

first question on here so forgive me if I make any mistakes, I will try to stick to the guidelines.
I am trying to write a PowerShell script that populates two arrays from data I read in via CSV file. I'm using the arrays to cross-reference directory names in order to rename each directory. One array contains the current name of the directory and the other array contains the new name.
This all seems to be working so far. I successfully create and populate the arrays, and using a short input and index lookup to check my work I can search one array for a current name and successfully retrieve the correct new name from the second array. However when I try to implement this code in a foreach loop that runs through every directory name, I can't lookup the array index (it keeps coming back as -1).
I used the code in the first answer found here as my template.
Read a Csv file with powershell and capture corresponding data . Here's my modification to the input lookup, which works just fine:
$input = Read-Host -Prompt "Merchant"
if($Merchant -contains $input)
{
Write-Host "It's there!"
$find = [array]::IndexOf($Merchant, $input)
Write-Host Index is $find
}
Here is my foreach loop that attempts to use the Index lookup, but returns -1 every time. However I know it's finding the file because it enters the if statement and prints "It's there!"
foreach($file in Get-ChildItem $targetDirectory)
{
if($Merchant -contains $file)
{
Write-Host "It's there!"
$find = [array]::IndexOf($Merchant, $file)
Write-Host Index is $find
}
}
I can't figure it out. I'm a PowerShell newb so maybe it's a simple syntax problem, but it seems like it should work and I can't find where I'm going wrong.
Your problem seems to be that $Merchant is a collection of file names (of type string), whereas $file is a FileInfo object.
The -contains operator expects $file to be a string, since $Merchant is a string array, and works as you expect (since FileInfo.ToString() just returns the file name).
IndexOf() isn't so forgiving. It recognizes that none of the items in $Merchant are of the type FileInfo, so it never finds $file.
You can either refer directly to the file name:
[array]::IndexOf($Merchant,$file.Name)
or, as #PetSerAl showed, convert $file to a string instead:
[array]::IndexOf($Merchant,[string]$file)
# or
[array]::IndexOf($Merchant,"$file")
# or
[array]::IndexOf($Merchant,$file.ToString())
Finally, you can call IndexOf() directly on the array, no need to use the static method:
$Merchant.IndexOf($file.Name)

Resources