Dynamic Variable & Objects - powershell

Trying to create a script that will read the contents of a directory containing a number of "paired" datasets containing customer data, for each customer there will be 2 datasets with the naming convention appearing consistently in the form: CustomerNo_DataType.csv where CustomerNo will always be numerical string value.
I've already written a crude version of this script with the customer numbers hard-coded so now I'm trying to improve on that - here's what I've got so far:
$files = Get-ChildItem "Path-to-data-files"
$files = $files.FullName
for ($i=0; $i -le $files.Count; $i++){
$thisFile = $files[$i].Split("\")
This leaves me with an array with the full pathname broken down into components so I grab the filename from the last position in the array
$thisFile = $thisFile[$thisFile.Count - 1]
...
}
I want to use the customer no to create a hashtable, so if the customer no in the filename was 12345 then I want to create a hashtable named $12345 - I'm not having any issues accessing the value, just not sure how to use it to name something.

Use Split-Path to get the file element of a path:
$file = Split-Path 'C:\path\to\some\file.txt' -Leaf
Use New-Variable if for some reason you need to define a variable name from a variable.
$customerNo = '12345'
New-Variable -Name $customerNo -Value #{}
However, I wouldn't recommend creating a bunch of dynamically named variables. It's usually a lot easier to handle if you create a "parent" hashtable for the dynamic names. You can have nested hashtables inside it if you need that:
$customerNo = '12345'
$customers = #{}
$customers[$customerNo] = #{}

Related

Powershell: storing variables to a file [duplicate]

I would like to write out a hash table to a file with an array as one of the hash table items. My array item is written out, but it contains files=System.Object[]
Note - Once this works, I will want to reverse the process and read the hash table back in again.
clear-host
$resumeFile="c:\users\paul\resume.log"
$files = Get-ChildItem *.txt
$files.GetType()
write-host
$types="txt"
$in="c:\users\paul"
Remove-Item $resumeFile -ErrorAction SilentlyContinue
$resumeParms=#{}
$resumeParms['types']=$types
$resumeParms['in']=($in)
$resumeParms['files']=($files)
$resumeParms.GetEnumerator() | ForEach-Object {"{0}={1}" -f $_.Name,$_.Value} | Set-Content $resumeFile
write-host "Contents of $resumefile"
get-content $resumeFile
Results
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
Contents of c:\users\paul\resume.log
files=System.Object[]
types=txt
in=c:\users\paul
The immediate fix is to create your own array representation, by enumerating the elements and separating them with ,, enclosing string values in '...':
# Sample input hashtable. [ordered] preserves the entry order.
$resumeParms = [ordered] #{ foo = 42; bar = 'baz'; arr = (Get-ChildItem *.txt) }
$resumeParms.GetEnumerator() |
ForEach-Object {
"{0}={1}" -f $_.Name, (
$_.Value.ForEach({
(("'{0}'" -f ($_ -replace "'", "''")), $_)[$_.GetType().IsPrimitive]
}) -join ','
)
}
Not that this represents all non-primitive .NET types as strings, by their .ToString() representation, which may or may not be good enough.
The above outputs something like:
foo=42
bar='baz'
arr='C:\Users\jdoe\file1.txt','C:\Users\jdoe\file2.txt','C:\Users\jdoe\file3.txt'
See the bottom section for a variation that creates a *.psd1 file that can later be read back into a hashtable instance with Import-PowerShellDataFile.
Alternatives for saving settings / configuration data in text files:
If you don't mind taking on a dependency on a third-party module:
Consider using the PSIni module, which uses the Windows initialization file (*.ini) file format; see this answer for a usage example.
Adding support for initialization files to PowerShell itself (not present as of 7.0) is being proposed in GitHub issue #9035.
Consider using YAML as the file format; e.g., via the FXPSYaml module.
Adding support for YAML files to PowerShell itself (not present as of 7.0) is being proposed in GitHub issue #3607.
The Configuration module provides commands to write to and read from *.psd1 files, based on persisted PowerShell hashtable literals, as you would declare them in source code.
Alternatively, you could modify the output format in the code at the top to produce such files yourself, which allows you to read them back in via
Import-PowerShellDataFile, as shown in the bottom section.
As of PowerShell 7.0 there's no built-in support for writing such as representation; that is, there is no complementary Export-PowerShellDataFile cmdlet.
However, adding this ability is being proposed in GitHub issue #11300.
If creating a (mostly) plain-text file is not a must:
The solution that provides the most flexibility with respect to the data types it supports is the XML-based CLIXML format that Export-Clixml creates, as Lee Dailey suggests, whose output can later be read with Import-Clixml.
However, this format too has limitations with respect to type fidelity, as explained in this answer.
Saving a JSON representation of the data, as Lee also suggests, via ConvertTo-Json / ConvertFrom-Json, is another option, which makes for human-friendlier output than XML, but is still not as friendly as a plain-text representation; notably, all \ chars. in file paths must be escaped as \\ in JSON.
Writing a *.psd1 file that can be read with Import-PowerShellDataFile
Within the stated constraints regarding data types - in essence, anything that isn't a number or a string becomes a string - it is fairly easy to modify the code at the top to write a PowerShell hashtable-literal representation to a *.psd1 file so that it can be read back in as a [hashtable] instance via Import-PowerShellDataFile:
As noted, if you don't mind installing a module, consider the Configuration module, which has this functionality built int.
# Sample input hashtable.
$resumeParms = [ordered] #{ foo = 42; bar = 'baz'; arr = (Get-ChildItem *.txt) }
# Create a hashtable-literal representation and save it to file settings.psd1
#"
#{
$(
($resumeParms.GetEnumerator() |
ForEach-Object {
" {0}={1}" -f $_.Name, (
$_.Value.ForEach({
(("'{0}'" -f ($_ -replace "'", "''")), $_)[$_.GetType().IsPrimitive]
}) -join ','
)
}
) -join "`n"
)
}
"# > settings.psd1
If you read settings.psd1 with Import-PowerShellDataFile settings.psd1 later, you'll get a [hashtable] instance whose entries you an access as usual and which produces the following display output:
Name Value
---- -----
bar baz
arr {C:\Users\jdoe\file1.txt, C:\Users\jdoe\file1.txt, C:\Users\jdoe\file1.txt}
foo 42
Note how the order of entries (keys) was not preserved, because hashtable entries are inherently unordered.
On writing the *.psd1 file you can preserve the key(-creation) order by declaring the input hashtable (System.Collections.Hashtable) as [ordered], as shown above (which creates a System.Collections.Specialized.OrderedDictionary instance), but the order is, unfortunately, lost on reading the *.psd1 file.
As of PowerShell 7.0, even if you place [ordered] before the opening #{ in the *.psd1 file, Import-PowerShellDataFile quietly ignores it and creates an unordered hashtable nonetheless.
This is a problem I deal with all the time and it drives me mad. I really think that there should be a function specifically for this action... so I wrote one.
function ConvertHashTo-CSV
{
Param (
[Parameter(Mandatory=$true)]
$hashtable,
[Parameter(Mandatory=$true)]
$OutputFileLocation
)
$hastableAverage = $NULL #This will only work for hashtables where each entry is consistent. This checks for consistency.
foreach ($hashtabl in $hashtable)
{
$hastableAverage = $hastableAverage + $hashtabl.count #Counts the amount of headings.
}
$Paritycheck = $hastableAverage / $hashtable.count #Gets the average amount of headings
if ( ($parity = $Paritycheck -is [int]) -eq $False) #if the average is not an int the hashtable is not consistent
{
write-host "Error. Hashtable is inconsistent" -ForegroundColor red
Start-Sleep -Seconds 5
return
}
$HashTableHeadings = $hashtable[0].GetEnumerator().name #Get the hashtable headings
$HashTableCount = ($hashtable[0].GetEnumerator().name).count #Count the headings
$HashTableString = $null # Strange to hold the CSV
foreach ($HashTableHeading in $HashTableHeadings) #Creates the first row containing the column headings
{
$HashTableString += $HashTableHeading
$HashTableString += ", "
}
$HashTableString = $HashTableString -replace ".{2}$" #Removed the last , added by the above loop in error
$HashTableString += "`n"
foreach ($hashtabl in $hashtable) #Adds the data
{
for($i=0;$i -lt $HashTableCount;$i++)
{
$HashTableString += $hashtabl[$i]
if ($i -lt ($HashTableCount - 1))
{
$HashTableString += ", "
}
}
$HashTableString += "`n"
}
$HashTableString | Out-File -FilePath $OutputFileLocation #writes the CSV to a file
}
To use this copy the function into your script, run it, and then
ConvertHashTo-CSV -$hashtable $Hasharray -$OutputFileLocation c:\temp\data.CSV
The code is annotated but a brief explanation of what it does. Steps through the arrays and hashtables and adds them to a string adding the required formatting to make the string a CSV file, then outputs that to a file.
The main limitation of this is that the Hashtabes in the array all have to contain the same amount of fields. To get around this if a hashtable has a field that doesnt contain data ensure it contains at least a space.
More on this can be found here : https://grumpy.tech/powershell-convert-hashtable-to-csv/

Concatenate two strings to form an existing variable

I have a folder with multiple PDFs I need to print to different printers. I've created variables for each shared printer and depending on the first 2 letters of the PDF the printing will go to the matching printer.
I'm having trouble concatenating 2 strings to form an existing variable to use it later in the printing call.
This is what I have now (all PDFs in the dir starts with 01 for now):
# SumatraPDF path
$SumatraExe = "C:\Users\Administrador.WIN-FPFTEJASDVR\AppData\Local\SumatraPDF\SumatraPDF.exe"
# PDFs to print path
$PDF = "C:\Program Files (x86)\CarrascocreditosPrueba2\CarrascocreditosPrueba2\DTE\BOL"
# Shared printers list
$01 = '\\192.168.1.70\epson'
$02 = '\\192.168.1.113\EPSON1050'
cd $PDF
While ($true) {
Get-ChildItem | Where {!$_.PsIsContainer} | Select-Object Name | %{
$Boleta = $_.Name
$CodSucursal = $Boleta.Substring(0,2)
$CodImpresora = '$' + $CodSucursal
Write-Host $CodImpresora -> This shows literal $01 on PS ISE
Write-Host $01 -> This show the shared printer path
}
Start-Sleep -Seconds 5
}
# Actual PDF printing...
#& $SumatraExe -print-to $CodImpresora $PDF
So basically I need to call an existing variable based on 2 strings. Probably this could be achieved with a Switch but that will be too extensive.
concatenating 2 strings to form an existing variable
That won't work in PowerShell, variable tokens are always treated literally.
I'd suggest you use a hashtable instead:
# Shared printers table
$Impresoras = #{
'01' = '\\192.168.1.70\epson'
'02' = '\\192.168.1.113\EPSON1050'
}
Then inside the loop:
$Boleta = $_.Name
$CodSucursal = $Boleta.Substring(0,2)
$Impresora = $Impresoras[$CodSucursal]
Although the language syntax don't support variable variable names, you can resolve variables by name using either the Get-Variable cmdlet:
# Returns a PSVariable object describing the variable $01
Get-Variable '01'
# Returns the raw value currently assigned to $01
Get-Variable '01' -ValueOnly
... or by querying the Variable: PSDrive:
# Same effect as `Get-Variable 01`
Get-Item Variable:\01
While these alternatives exist, I'd strongly suggest staying clear of using them in scripts - they're slow, makes the code more complicated to read, and I don't think I've ever encountered a situation in which using a hashtable or an array wasn't ultimately easier :)

CSV input, powershell pulling $null value rows from targeted column

I am trying to create a script to create Teams in Microsoft Teams from data in a CSV file.
The CSV file has the following columns: Team_name, Team_owner, Team_Description, Team_class
The script should grab Team_name row value and use that value to create a variable. Use that variable to query if it exists in Teams and if not, create it using the data in the other columns.
The problem I am having is my foreach loop seems to be collecting rows without values. I simplified the testing by first trying to identify the values and monitoring the output.
Here is the test script
$Team_infocsv = Import-csv -path $path Teams_info.csv
# $Team_infocsv | Foreach-object{
foreach($line in $Team_infocsv){
$owner = $line.Team_owner
Write-Host "Team Owner: $owner"
$teamname = $line.Team_name
Write-Host "Team Name: $teamname"
$team_descr = $line.Team_Description
Write-Host "Team Description: $team_descr"
$teamclass = $line.Team_class
Write-Host "Team Class: $teamclass"
}
I only have two rows of data but yet returned are the two lines as requested with extra output (from rows) without values.
There's no problem with your code per se, except:
Teams_info.csv is specified in addition to $path after Import-Csv -Path, which I presume is a typo, however.
$path could conceivably - and accidentally - be an array of file paths, and if the additional file(s) has entirely different columns, you'd get empty values for the first file's columns.
If not, the issue must be with the contents of Teams_info.csv, so I suggest you examine that; piping to Format-Custom as shown below will also you help you detect the case where $path is unexpectedly an array of file paths:
Here's a working example of a CSV file resembling your input - created ad hoc - that you can compare to your input file.
# Create sample file.
#'
"Team_name","Team_owner","Team_Description","Team_class"
"Team_nameVal1","Team_ownerVal1","Team_DescriptionVal1","Team_classVal1"
"Team_nameVal2","Team_ownerVal2","Team_DescriptionVal2","Team_classVal2"
'# > test.csv
# Import the file and examine the objects that get created.
# Note the use of Format-Custom.
Import-Csv test.csv test.csv | Format-Custom
The above yields:
class PSCustomObject
{
Team_name = Team_nameVal1
Team_owner = Team_ownerVal1
Team_Description = Team_DescriptionVal1
Team_class = Team_classVal1
}
class PSCustomObject
{
Team_name = Team_nameVal2
Team_owner = Team_ownerVal2
Team_Description = Team_DescriptionVal2
Team_class = Team_classVal2
}
Format-Custom produces a custom view (a non-table and non-list view) as defined by the type of the instances being output; in the case of the [pscustomobject] instances that Import-Csv outputs you get the above view, which is a convenient way of getting at least a quick sense of the objects' content (you may still have to dig deeper to distinguish empty strings from $nulls, ...).

Reading multiple variables into an array through a loop

I'm sure this is simple but I am just trying to wrap my head around it. I have an XML file that looks like this:
<software>
<program>Bob</program>
<program>Reader</program>
<program>Hello</program>
<program>Java</program>
</software>
I am then pulling it into the script like this
[xml]$xml = Get-Content configuration.xml
foreach( $entry in $xml.software)
{
$arrayofsoftware = $entry.program
}
First thing to note is I don't know how many program entries will be in the XML. What I am looking to do is put all of that software into some sort of array. I then need to seperate it later on into seperate variables (as I need to pass each one as a switch to a command line).
Can anyone throw me in the right direction?
This will create a collection of program names and assign them to the $arrayofsoftware variable.
[array]$arrayofsoftware = $xml.software.program
To create a separate variable for each value, use the New-Variable cmdlet:
for($i=0; $i -lt $arrayofsoftware.count; $i++)
{
New-Variable -Name "arrayofsoftware$i" -Value $arrayofsoftware[$i]
}
# get a list of arrayofsoftwar variables
Get-Variable arrayofsoftwar*
Name Value
---- -----
arrayofsoftware {Bob, Reader, Hello, Java}
arrayofsoftware0 Bob
arrayofsoftware1 Reader
arrayofsoftware2 Hello
arrayofsoftware3 Java

PowerShell: How can I traverse through registry values named "SQLArg1", "SQLArg2" etc.?

I am working on a PowerShell function that is supposed to check if two values in the registry named "SQLArg4" and "SQLArg5" (or two other numbers) are set to a certain value content and set them if not.
The problem is I cannot enumerate the values to traverse through them to compare all of them and then add my two value contents if they are not present.
I tried creating a string out of "SQLArg" and a $i index but PowerShell would not allow me to use that string as a field of a variable.
Any ideas?
It sounds like you only need to work with one registry key (no recursion). Try this code. It will get all the values under $key that start with "SQLArgs" and store them in $values. It loops through a number sequence and tests for the existence of the key values named SQLArgs#. When it finds one it will set the key value data.
$key = "HKCU:\Andy"
$values = Get-ItemProperty -Path $key -Name SQLArg*
1..20 | % {
if ($values."SQLArg$_" -ne $null) {
Set-ItemProperty -Path $key -Name "SQLArg$_" -Value "Powershell Rocks"
}
}