ConvertFrom-StringData fails casting to Hashtable - arrays

I just tried to adapt the scripting guy's example to read data from a textfile and parse a hashtable using ConvertFrom-StringData:
$PSVersionTable.PSVersion;
[String] $loginsFileName = "$HOME\Logins.txt";
Get-Content $loginsFileName;
[Hashtable] $logins = Get-Content -Path $loginsFileName | ConvertFrom-StringData;
Write-Host got $logins.count lines from $loginsFileName;
$logins.GetEnumerator() | Sort-Object Name;
I got screwed up:
Major Minor Build Revision
----- ----- ----- --------
3 0 -1 -1
Lisa=GanzGeheim
Susanne=ganzgeheim
Fritz=geheim
Hans=Geheim
Der Wert "System.Object[]" vom Typ "System.Object[]" kann nicht in den Typ "System.Collections.Hashtable" konvertiert werden.
In Zeile:11 Zeichen:1
+ [Hashtable] $logins = Get-Content -Path $loginsFileName | ConvertFrom-StringData ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : MetadataError: (:) [], ArgumentTransformationMetadataException
+ FullyQualifiedErrorId : RuntimeException
got 0 lines from E:\Users\Christian\Logins.txt
My guess: The piping goes wrong. I tried to stress out that Get-Content delivers an array by embracing (Get-Content -Path $loginsFileName) – without any bettering. Can anybody please give me some hint?

Sorry about misleading you earlier. The issue you are having is that you are getting an array of single key hashtables because of Get-Content. For what I think you are looking for you should read in the file as one string. Assuming you have at least PowerShell v3:
$logins = Get-Content -Path $loginsFileName -Raw | ConvertFrom-StringData;
This was you get one single hashtable as supposed to an array of hashtables. This gets me once and a while since PowerShell groups the output well.
-Raw is a feature of 3.0. If you do not have access to 3.0 than either of these solutions would suffice.
$logins = Get-Content -Path $loginsFileName | Out-String | ConvertFrom-StringData;
$logins = ((Get-Content -Path $loginsFileName) -join "`r`n") | ConvertFrom-StringData;

Related

How to encrypt my program config file using PowerShell to prevent users from seen the content?

I have a small config file for a program that this code creates, the file is created by converting the $Data variable into Json format txt file and reads back from it.
I would like the file to be encrypted and still be able to decrypt and read from it.
$path = "test.txt"
$Data = #{
File = "test.exe"
Folder = "c:\temp\"
count = "4"
}
$Data | ConvertTo-Json | Add-Content -Path $path
$NewData = Get-Content -Path $path -Raw | ConvertFrom-Json
$NewData.File
$NewData.Folder
$NewData.count
Any suggestions on how to add simple encryption and decryption?
Maybe using ConvertTo-SecureString.. I tried but cant get it to work (not even close).
I want to thank #santiago-squarzon and all who contributed to this post.
After a VERY long night of trial and error basically attempting to translate the Microsoft c# examples and snippets to PowerShell I finally found a working solution.
Also a big thank you to
Luke Orellana for this article https://www.altaro.com/msp-dojo/encrypt-password-powershell/
Adam Bertram for this eye opening article https://mcpmag.com/articles/2017/07/20/save-and-read-sensitive-data-with-powershell.aspx
Here is the final solution for anyone that wants to do it like me!
$tempfolder = $env:temp
$tmpkey = {110
52
114
225
64
235
208
235
242
44
101
16
80
94
97
252} | Set-Content $tempfolder\newkey.key # I have generated this 16 byte random key using key generation script
# with the help from " https://www.altaro.com/msp-dojo/encrypt-password-powershell/ ""
$key = Get-Content $tempfolder\newkey.key
$file = "test.txt"
$Data = #{
File = "test.exe"
Folder = "c:\temp\"
count = "4"
}
$perm = $data | ConvertTo-Json
$perm | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString -key $key | Set-Content -Path $file
# this encrypted the data into my test.txt file no to show how it looks inside
$tmp = Get-Content -path $file
echo $tmp
# now to retrieve it and convert back to usable text
$NewData = Get-Content -Path $file | ConvertTo-SecureString -key $key
$LoadedData = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR((($NewData)))) | ConvertFrom-Json
$LoadedData.File
$LoadedData.Folder
$LoadedData.Count

Powershell Format Arrays to two dimensional Arrays

i had a problem a few days ago where my script that is supposed to extract all hostnames from an AD OU and then check for the space used and free space on disks for every single host. Since this is the first time i do something with powershell i ran into many problems. The Problem that i got now is that the script cant find the hostnames listed in an array. I think i found out why it wont work because it uses the wrong hostname.
Error message i get for every hostname:
Write-Warning : Es wurde kein Positionsparameter gefunden, der das Argument "#{Name=BUCHHOLZMVZ}" akzeptiert.
In Zeile:16 Zeichen:5
+ Write-Warning "Server " $server "nicht erreichbar"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Write-Warning], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.WriteWarningCommand
When i only use the command to get all hostnames of all servers i get:
Name
----
someserver
someserver1
someserver2
(and so on...)
Here is the script:
$servers = Get-ADComputer -Filter * -SearchBase "OU=ServerOU, DC=somedomain, DC=somedomain, DC=somedomain" | Select-Object Name
$allDisks = foreach ($server in $servers)
{
try {
Get-WmiObject Win32_LogicalDisk -ComputerName $server -Filter DriveType=3 -ErrorAction Stop |
Select-Object #{'Name'='ComputerName'; 'Expression'={$server}},
DeviceID,
#{'Name'='Size'; 'Expression'={[math]::truncate($_.size / 1GB)}},
#{'Name'='Freespace'; 'Expression'={[math]::truncate($_.freespace / 1GB)}}
}
catch {
Write-Warning "Server " $server "nicht erreichbar"
Continue
}
}
$allDisks |Export-Csv C:\Servers.csv -NoTypeInformation
Use the following for your first line:
$servers = Get-ADComputer -Filter * -SearchBase "OU=ServerOU, DC=somedomain, DC=somedomain, DC=somedomain" |
Select-Object -Expand Name
Using Select-Object without -Expand or -ExpandProperty outputs an object that contains properties and values. If you only want to output values of the selected properties you must use -Expand or member access ($servers.Name).

Powershell Get-Content + Array

how's it going?
I'm new on Powershell and I'm trying to simplify my code in order that I need to perform the same action in two files, the only thing that changes is the File Name and ReadCount size (15000 for the first file and 50000 for the second one).
When I run it the error shows:
Get-Content : An object at the specified path
C:\Folder\08_configuration_items 11_CI-Contract-new[0].csv does not
exist, or has been filtered by the -Include or -Exclude parameter. At
line:2 char:7
+ $i=0; Get-Content "C:\Folder\$fileArray[$len].csv" -ReadCount $sizeA ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (System.String[]:String[]) [Get-Content], Exception
+ FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetContentCommand
Get-Content : An object at the specified path
C:\Folder\08_configuration_items 11_CI-Contract-new[0]_1.csv does not
exist, or has been filtered by the -Include or -Exclude parameter. At
line:3 char:20
+ ... bookContent = Get-Content "C:\Folder\$fileArray[$len]_1.csv" | Selec ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (System.String[]:String[]) [Get-Content], Exception
+ FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetContentCommand
That's the code, not sure if I'm accessing the Array using the right way on Powershell.
$sizeArray = #(15000,50000)
$fileArray = #("08_configuration_items", "11_CI-Contract-new")
for($len=0; $len -le 1; $len++) {
$i=0; Get-Content "C:\Folder\$fileArray[$len].csv" -ReadCount $sizeArray[$len] | %{$i++; $_ | Out-File "C:\Folder\$fileArray[$len]_$i.csv" -Encoding "UTF8"}
$WorkbookContent = Get-Content "C:\Folder\$fileArray[$len]_1.csv" | Select -Index 0
for($j=2; $j -le $i; $j++) {
$CurrentFileContent = Get-Content "C:\Folder\$fileArray[$len]_$j.csv"
#($WorkbookContent, $CurrentFileContent) | Set-Content "C:\Folder\$fileArray[$len]_$j.csv"
}
}
Any ideias?
Thanks a lot
The problem here is with string interpolation. A variable name within a string will expand up until it reaches a special character in that name. Then it will append the remainder of the string and any interpolated strings afterwards. This commonly happens with the . character when accessing a property of an object within a string. A simple solution is to use the subexpression operator ($()).
Get-Content "C:\Folder\$($fileArray[$len]).csv"
An alternative is to build the path string another way and then pass it into the command. The method below uses the format operator (-f).
$Path = "C:\Folder\{0}.csv" -f $fileArray[$len]
Get-Content $Path
Your code with the subexpression operator added will look like the following:
$sizeArray = #(15000,50000)
$fileArray = #("08_configuration_items", "11_CI-Contract-new")
for($len=0; $len -le 1; $len++) {
$i=0; Get-Content "C:\Folder\$($fileArray[$len]).csv" -ReadCount $sizeArray[$len] | %{$i++; $_ | Out-File "C:\Folder\$($fileArray[$len])_$i.csv" -Encoding "UTF8"}
$WorkbookContent = Get-Content "C:\Folder\$($fileArray[$len])_1.csv" | Select -Index 0
for($j=2; $j -le $i; $j++) {
$CurrentFileContent = Get-Content "C:\Folder\$($fileArray[$len])_$j.csv"
#($WorkbookContent, $CurrentFileContent) | Set-Content "C:\Folder\$($fileArray[$len])_$j.csv"
}
}
You can see this behavior on a simpler scale using your $fileArray variable.
$filearray
08_configuration_items
11_CI-Contract-new
# Notice how the [0] gets appended to the string-cast $fileArray
"$filearray[0]"
08_configuration_items 11_CI-Contract-new[0]
$filearray[0]
08_configuration_items
"$($filearray[0])"
08_configuration_items
Since $fileArray is an array of strings, you have another unintended effect. With "$fileArray[0]", $fileArray will be interpolated and converted to a string output rather than an array. PowerShell by default will join array elements by a single space when casting as a string. So the resulting output format is arrayItem1 arrayItem2 arrayItem3[0]. [0] is not included as part of the variable evaluation.

Manipulating String Values within an Object using PowerShell

My goal is to take two objects (created by importing CSVs) that have host names and compare one list to the other and show what's missing from each.
Before I can do the comparison I need to manipulate the host names stored within the object(s). The first step is to use regular expressions to remove (-replace) unnecessary text and then set all host names to lowercase (ToLower()).
I'm not very proficient with modifying existing objects and keeping them "intact", so I'm hoping someone could help me with this.
Here's an example of the data stored within the CSV. The header is on line 7 and each line of data is stored like:
...
7 "name","IP","OSType"
8 "WCSMserver.com","10.10.10.10","OSX"
9 "SERVER2.com","11.11.11.11","Windows"
10 "windowsserver # SERVER2.com","11.11.11.13","Windows"
11 "winner.comSERVER2.com","11.11.11.12","Windows"
...
Here's an example of what I'm trying to do so far (just replacing the name property values):
function ReadExcelReport() {
$global:ConvertedQReportTest = $PSScriptRoot + "\" + "AllSources.csv"
$global:QReportObject = Get-Content -Path $global:ConvertedQReportTest |
Select-Object -Skip 7 |
ConvertFrom-Csv
}
ReadExcelReport
$global:QReportObject.name = $global:QReportObject.name | ForEach-Object {
#($global:QReportObject.name)
$_ -replace 'WCSM \- ' `
-replace '.*?# '`
-replace '.*?#'`
-replace '.*?\:\:.*?'`
-replace '\.cooper\.winner\.com'`
-replace '\.winner\.com'
}
By doing $global:QReportObject.name | ForEach-Object you loop the names of the objects and not the objects.
I've simplified your script a bit (for readability):
$csv = #"
"name"
"WCSMserver-remove this-com"
"SERVER2.com","11.11.11.11"
"windowsserver-remove this-"
"winner.comSERVER2.com"
"#
$global:QReportObject = $csv | ConvertFrom-Csv
$global:QReportObject | Out-Default
$global:QReportObject | ForEach-Object {
$_.name = $_.name -replace '-remove this-'
$_.name = $_.name.ToLower()
}
$global:QReportObject | Out-Default
This will output:
name
----
WCSMserver-remove this-com
SERVER2.com
windowsserver-remove this-
winner.comSERVER2.com
name
----
wcsmservercom
server2.com
windowsserver
winner.comserver2.com

What is this powershell object? Array, hash, etc?

I have the following in a script:
$Loc = Read-Host "Please select a location number"
$ImpCSV = Import-Csv -Delimiter ";" -Path "file.csv" -Header loc,add | Where-Object {$_.loc -eq "$Loc"}
Foreach ($CIM in $ImpCSV) {$LocInfo = $CIM}
This seems to create some sort of hash/array/something variable for $Loc=1. If I enter:
$LocInfo.add
It gives me the value of that key(?) as it should in a hash table, but if I do:
$LocInfo
I get:
loc        : [1]
add       : [some value]
Which is not the usual hash output. This is what one should look like:
Name         Value
----              -----
loc              [1]
add            [some value]
The issue this is first causing me is that if I try to edit an entry using $LocInfo.Set_Item("add", "HI"), it does nothing. Also, I created a table to display those variables creating new-objects, but the new -property command won't take $LocInfo.add, returning a "#{loc=1; add=some value}" if not giving me a syntax error. What I had to do was:
Set-Variable -Name add -Value $LocInfo.add
This sets add to [some value] and then I can put that in the new-object field without any issue.
This is additionally problematic because my script loops so they can add another location (loc) number (let's say 2) and pull up that location's data as necessary. If the new location they are searching has say the add (Address) missing, instead of creating a blank $LocInfo.add and then a blank $add, it skips it, meaning when the new-object is created again, it uses the $add from the previous run, [some value]; ie both loc 1 and 2 have the same $add value.
What that heck did I create, and how can I do anything with it? Is there a better way to pull variables from a CSV file that won't screw me up like this? Did I break Powershell? I've looked all over and I can't find this kind of variable arrangement.
This is the error I get when I try to do a Set_Item:
Method invocation failed because [System.Management.Automation.PSCustomObject] does not contain a method named 'Set_Item'.
At line:1 char:1
+ $LocInfo.Set_Item("add", "HI")
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo : InvalidOperation: (Set_Item:String) [], RuntimeException
        + FullyQualifiedErrorId : MethodNotFound
I've created objects using New-Object, and even those display like a hash table, ie with a header and multiple columns depending on the object size.
First thing: If you are ever not sure about the type of an object you can just check and object's type with GetType(). Import-Csv command returns a System.Object[] where a command like this #{Letter="Test"}.GetType().FullName would return System.Collections.Hashtable. .FullName returns the just the strings I am using in these examples.
Appears that you are trying to collect your output with the Where-Object using this line Foreach ($CIM in $ImpCSV) {$LocInfo = $CIM} The couple of issues I see with that are your keep overwriting $LocInfo with $CIM. At the end of the loop you will only have one variable. If you are trying to keep all of them would need to append each item and declare $LocInfo as an array.
$LocInfo = #()
....
Foreach ($CIM in $ImpCSV) {$LocInfo += $CIM}
But unless you have more code that you are not showing this appears redundant as you would have $LocInfo and $ImpCSV populated with the same data. You could just do away with the for loop altogether and just do this.
$Loc = Read-Host "Please select a location number"
$ImpCSV = Import-Csv -Delimiter ";" -Path "file.csv" -Header loc,add | Where-Object {$_.loc -eq "$Loc"}
I have changed nothing except remove the last line.
Does converting the result of import-csv into custom object like this help?
$ImpCSV = Import-Csv -Delimiter ";" -Path "file.csv" -Header loc,add | Where-Object {$_.loc -eq "$Loc"} | %{[pscustomobject]#{LOC=$_.LOC;ADD=$_.ADD}}
or
$ImpCSV = Import-Csv -Delimiter ";" -Path "file.csv" -Header loc,add | Where-Object {$_.loc -eq "$Loc"} | %{new-object -TypeName PSCustomObject -Property #{LOC=$_.LOC;ADD=$_.ADD}}
Import-Csv reads a CSV file and transforms it into a list of custom objects with the CSV columns as properties. However, objects with just 2 properties should be displayed in table format by default:
PS C:\> cat 'C:\test.csv'
1,foo
2,bar
3,baz
PS C:\> $csv = Import-Csv 'C:\test.csv' -Header loc,add | ? {$_.loc -eq '2'}
PS C:\> $csv
loc add
--- ---
2 bar
You should only get output in list format if you pipe the object(s) into the Format-List cmdlet:
PS C:\> $csv | Format-List
loc : 2
add : bar
or if the objects have 5 or more properties:
PS C:\> cat 'C:\test2.csv'
1,foo,a,a,a
2,bar,b,b,b
3,baz,c,c,c
PS C:\> Import-Csv 'c:\test2.csv' -Header loc,add,f3,f4 | ? {$_.loc -eq '2'}
loc add f3 f4
--- --- -- --
2 bar b b
PS C:\> Import-Csv 'c:\test2.csv' -Header loc,add,f3,f4,f5 | ? {$_.loc -eq '2'}
loc : 2
add : bar
f3 : b
f4 : b
f5 : b
You can enforce tabular output for objects that would normally be displayed in list format by piping them into the Format-Table cmdlet:
PS C:\> Import-Csv 'C:\test2.csv' -Header loc,add,f3,f4,f5 |
>> ? {$_.loc -eq '2'} | Format-Table
>>
loc add f3 f4 f5
--- --- -- -- --
2 bar b b b
Note, however, that formatting cmdlets like Format-List or Format-Table are intended for displaying data. Don't use them if you want to further process the object(s).

Resources