Powershell parsing a properties file that contains colons - powershell

If I have a .properties file that contains directories (which contain colons):
some_dir=f:\some\dir\etc
another_dir=d:\dir\some\bin
and then use ConvertFrom-StringData to convert Key=Value pairs from said properties file to a hash table:
$props_file = Get-Content "F:\dir\etc\props.properties"
$props = ConvertFrom-StringData ($props_file)
$the_dir = $props.'some_dir'
Write-Host $the_dir
Powershell throws an error (doesn't like colons):
ConvertFrom-StringData : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'StringData'. Specified method is not supported.
At line:3 char:32
+ $props = ConvertFrom-StringData <<<< ($props_file)
+ CategoryInfo : InvalidArgument: (:) [ConvertFrom-StringData], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgument,Microsoft.PowerShell.Commands.ConvertFromStringDataCommand
How do you get round this? I'd like to be able to just refer to the directories using the . notation:
$props.'some_dir'

Colons have nothing to do with the error you get. And yes, it can be achieved using ConvertFrom-StringData but, as already mentioned, you're feeding it an array instead of a string. Moreover, you need paths with double backslashes in your file because single backslashes are interpreted as escape characters.
Here's how to fix your code:
# Reading file as a single string:
$sRawString = Get-Content "F:\dir\etc\props.properties" | Out-String
# The following line of code makes no sense at first glance
# but it's only because the first '\\' is a regex pattern and the second isn't. )
$sStringToConvert = $sRawString -replace '\\', '\\'
# And now conversion works.
$htProperties = ConvertFrom-StringData $sStringToConvert
$the_dir = $htProperties.'some_dir'
Write-Host $the_dir

ConvertFrom-StringData expects a string and you are feeding it an array Get-Content cmdlet. Change $props_file to:
$props_file = (Get-Content "F:\dir\etc\props.properties") | Out-String
and you will get another error:
ConvertFrom-StringData : parsing "f:\some\dir\etc" - Unrecognized escape sequence \s.
You can get around it like so:
$props_file = Get-Content "F:\dir\etc\props.properties"
$props = #{}
$props_file | % {
$s = $_ -split "="
$props.Add($s[0],$s[1])
}
$the_dir = $props.'some_dir'
Write-Host $the_dir
Result:
f:\some\dir\etc

You could parse the file as it's loaded, and populate an empty hashtable line-by-line.
$props = #{}
GC "F:\dir\etc\props.properties" | %{$props.add($_.split('=')[0],$_.split('=')[1])}

Related

Century10. Underthewire.tech walkthrough

The password for Century10 is the 161st word within the file on the desktop.
NOTE:
- The password will be lowercase no matter how it appears on the screen.
*The question above is where i am facing my challenges. I tried the command below. *
Get-Content C:\Users\Century9\Desktop\Word_File.txt | Select-Object -Index 161
Result was nil. I understand that i need to assign a value to the string as it is now seen as one whole entity. But how do i do it ?
If the token of interest is the 161st word in the file, use the following approach, which splits the file into words irrespective of line breaks[1]:
$pass = (-split (Get-Content -Raw Word_File.txt))[160]
Append .ToLower() if you want to convert the token to all-lowercase.
Note that the above loads the entire file into memory as a single string, using -Raw.
Since array indices are 0-based, it is index [160] that returns the 161st element.
The unary form of the -split operator splits the input into an array of tokens by whitespace.
Note: If you want to split by the stricter definition of what constitutes a word in a regular-expression context, use the following instead:
$pass = ((Get-Content -Raw Word_File.txt) -split '\W+' -ne '')[160]
[1] If your input file contains each word on its own line:
Your solution was on the right track, except that you should pass 160 to Select-Object -Index, because the -Index parameter expects 0-based indices, not 1-based line numbers:
# Extract the 161st line.
$pass = Get-Content Word_File.txt | Select-Object -Index 160
To convert to lowercase:
$pass = (Get-Content Word_File.txt | Select-Object -Index 160).ToLower()
The above will fail if the input file has fewer than 161 lines (with error message You cannot call a method on a null-valued expression).
If you prefer to receive no output quietly instead, use the following (which uses built-in aliases select for Select-Object and foreach for ForEach-Object for brevity):
$pass = Get-Content Word_File.txt | select -Index 160 | foreach ToLower
Try running this:
((Get-Content -Path C:\Users\Century9\Desktop\Word_File.txt -TotalCount 161)[-1]).ToLower()

How to get Get-ChildItem to handle path with non-breaking space

I have the following code that works for most files. The input file (FoundLinks.csv) is a UTF-8 file with one file path per line. It is full paths of files on a particular drive that I need to process.
$inFiles = #()
$inFiles += #(Get-Content -Path "C:\Users\sw_admin\FoundLinks.csv")
foreach ($inFile in $inFiles) {
Write-Host("Processing: " + $inFile)
$objFile = Get-ChildItem -LiteralPath $inFile
New-Object PSObject -Prop #{
FullName = $objFile.FullName
ModifyTime = $objFile.LastWriteTime
}
}
But even though I've used -LiteralPath, it continues to not be able to process files that have a non-breaking space in the file name.
Processing: q:\Executive\CLC\Budget\Co  2018 Budget - TO Bob (GA Prophix).xlsx
Get-ChildItem : Cannot find path 'Q:\Executive\CLC\Budget\Co  2018 Budget - TO Bob (GA Prophix).xlsx'
because it does not exist.
At ListFilesWithModifyTime.ps1:6 char:29
+ $objFile = Get-ChildItem <<<< -LiteralPath $inFile
+ CategoryInfo : ObjectNotFound: (Q:\Executive\CL...A Prophix).xlsx:String) [Get-ChildItem], ItemNotFound
Exception
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand
I know my input file has the non-breaking space in the path because I'm able to open it in Notepad, copy the offending path, paste into Word, and turn on paragraph marks. It shows a normal space followed by a NBSP just before 2018.
Is PowerShell not reading in the NBSP? Am I passing it wrong to -LiteralPath? I'm at my wit's end. I saw this solution, but in that case they are supplying the path as a literal in the script, so I can't see how I could use that approach.
I've also tried: -Encoding UTF8 parameter on Get-Content, but no difference.
I'm not even sure how I can check $inFile in the code just to confirm if it still contains the NBSP.
Grateful for any help to get unstuck!
Confirmed that $inFile has NBSP
Thank you all! As per #TheMadTechnician, I have updated the code like this, and also reduced my input file to only the one file having a problem.
$inFiles = #()
$inFiles += #(Get-Content -Path "C:\Users\sw_admin\FoundLinks.csv" -Encoding UTF8)
foreach ($inFile in $inFiles) {
Write-Host("Processing: " + $inFile)
# list out all chars to confirm it has an NBSP
$inFile.ToCharArray()|%{"{0} -> {1}" -f $_,[int]$_}
$objFile = Get-ChildItem -LiteralPath $inFile
New-Object PSObject -Prop #{
FullName = $objFile.FullName
ModifyTime = $objFile.LastWriteTime
}
}
And so now I can confirm that $inFile in fact still contains the NBSP just as it gets passed to Get-ChildItem. Yet Get-ChildItem says the file does not exist.
More I've tried:
Same if I use Get-Item instead of Get-ChildItem
Same if I use -Path instead of -LiteralPath
Windows explorer and Excel can deal with the file successfully.
I'm on a Windows 7 machine, Powershell 2.
Thanks again for all the responses!
It's still unclear why Sandra's code didn't work: PowerShell v2+ is capable of retrieving files with paths containing non-ASCII characters; perhaps a non-NTFS filesystem with different character encoding was involved?
However, the following workaround turned out to be effective:
$objFile = Get-ChildItem -Path ($inFile -replace ([char] 0xa0), '?')
The idea is to replace the non-breaking space char. (Unicode U+00A0; hex. 0xa) in the input file path with wildcard character ?, which represents any single char.
For Get-ChildItem to perform wildcard matching, -Path rather than -LiteralPath must be used (note that -Path is actually the default if you pass a path argument positionally, as the first argument).
Hypothetically, the wildcard-based paths could match multiple files; if that were the case, the individual matches would have to be examined to identify the specific match that has a non-breaking space in the position of the ?.
Get-ChildItem is for listing children so you would be giving it a directory, but it seems you are giving it a file, so when it says it cannot find the path, it's because it can't find a directory with that name.
Instead, you would want to use Get-Item -LiteralPath to get each individual item (this would be the same items you would get if you ran Get-ChildItem on its parent.
I think swapping in Get-Item would make your code work as is.
After testing, I think the above is in fact false, so sorry for that, but I will leave the below in case it's helpful, even though it may not solve your immediate problem.
But let's take a look at how it can be simplified with the pipeline.
First, you're starting with an empty array, then calling a command (Get-Content) which likely already returns an array, wrapping that in an array, then concatenating it to the empty one.
You could just do:
$inFiles = Get-Content -Path "C:\Users\sw_admin\FoundLinks.csv"
Yes, there is a chance that $inFiles will contain only a single item and not an array at all.
But the nice thing is that foreach won't mind one bit!
You can do something like this and it just works:
foreach ($string in "a literal single string") {
Write-Host $string
}
But Get-Item (and Get-ChildItem for that matter) accept pipeline input, so they accept multiple items.
That means you could do this:
$inFiles = Get-Content -Path "C:\Users\sw_admin\FoundLinks.csv" | Get-Item
foreach ($inFile in $inFiles) {
Write-Host("Processing: " + $inFile)
New-Object PSObject -Prop #{
FullName = $inFile.FullName
ModifyTime = $inFile.LastWriteTime
}
}
But even more than that, there is a pipeline-aware cmdlet for processing items, called ForEach-Object, to which you pass a [ScriptBlock], in which $_ represents the current item, so we could do it like this:
Get-Content -Path "C:\Users\sw_admin\FoundLinks.csv" |
Get-Item |
ForEach-Object -Process {
Write-Host("Processing: " + $_)
New-Object PSObject -Prop #{
FullName = $_.FullName
ModifyTime = $_.LastWriteTime
}
}
All in one pipeline!
But further, you're creating a new object with the 2 properties you want.
PowerShell has a nifty cmdlet called Select-Object which takes an input object and returns a new object containing only the properties you want; this would make for a cleaner syntax:
Get-Content -Path "C:\Users\sw_admin\FoundLinks.csv" |
Get-Item |
Select-Object -Property FullName,LastWriteTime
This is the power of the the pipeline passing real objects from one command to another.
I realize this last example does not write the processing message to the screen, however you could re-add that in if you wanted:
Get-Content -Path "C:\Users\sw_admin\FoundLinks.csv" |
Get-Item |
ForEach-Object -Process {
Write-Host("Processing: " + $_)
$_ | Select-Object -Property FullName,LastWriteTime
}
But you might also consider that many cmdlets support verbose output and try to just add -Verbose to some of your existing cmdlets. Sadly, it won't really help in this case.
One final note, when you pass items to the filesystem cmdlets via pipeline, the parameter they bind to is in fact -LiteralPath, not -Path, so your special characters are still safe.
I just run into the same issue. Looks like get-childitem ak gci expects the path in unicode (UTF-16). So either convert the csv file into unicode or convert the lines that include the path as unicode within your script.
Testet on PS 5.1.22621.608

I don't understand using get-content on a csv

How come when I do (Get-Content $CSV1)[2] in the Power-shell console on any .csv file I get the field but when I do it in the ISE I get errors like this.
$CSV1 = Import-Csv "C:\Users\Administrator\Documents\lab8\usersF.csv"
(Get-Content $CSV1)[2]
usersF.csv contents
GivenName
Ylnum
Dexcd
Igbos
Rzjlr
Errors
Cannot index into a null array.
At C:\Users\Administrator\Documents\lab8\ovie_lab7_merged.ps1:5 char:1
+ (Get-Content $CSV1)[2]
+ ~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : NullArray
I've tried the solution #TheIncorrigible1 gave me and it worked, but now I'd like to export that data to a .csv and instead of the string I got the string length in the .csv file how can I fix this?
$CSV1 = Import-Csv -Path "C:\Users\Administrator\Documents\lab8\usersF.csv"
$test = $CSV1.GivenName[4]
$test | Export-Csv "C:\Users\Administrator\Documents\lab8\test.csv"
#TYPE System.String
Length
5
So the problem you're running into is a simple type understanding problem.
Import-Csv takes the Path you provide to it and imports that into a PSCustomObject. You're trying to Get-Content -Path [PSCustomObject], but that is not a supported type for the -Path argument (if you read the help topic, it expects a [String[]]).
If you want that first line, you should do something like:
$Path = 'C:\Users\Administrator\Documents\lab8\usersF.csv'
#(Get-Content -Path $Path)[1] #Arrays are zero-indexed
Alternatively,
$Csv = Import-Csv -Path $Path
$Csv.GivenName[0]
To address your edit, you aren't calling Export-Csv properly. It expects a PSCustomObject, but you're passing it a single string. I'd suggest using Out-File instead.
$CSV1 = Import-Csv -Path C:\Users\Administrator\Documents\lab8\usersF.csv
$test = $CSV1.GivenName[4]
$test |
OutFile -FilePath C:\Users\Administrator\Documents\lab8\test.csv

beginner Powershell Script Error

To load a text file of 3 ip addresses comma separated values, into an array, and then have the contents in the array changed for every 3rd octet of the ip address, and then exported back to a csv or text file.
##load file to an array
$ipFileName="C:\Users\HarmanGrewal\Google Drive\win213\assignment2\IP.txt"
$array1=#()
$array1=Get-Content $ipFileName -Delimiter ","
#now we have the contents in an array
$count=0
foreach($i in $array1){
$array1[$count] = $array1[$count] -replace "\.\d{1}\.",".2."
$count++
}
Get-Content $array1 | export-csv -path "C:\Users\HarmanGrewal\test.txt"
A successful exportation of data to a csv file.
An empty csv file instead.
Get-Content : Cannot find path 'C:\Users\HarmanGrewal\192.168.2.10,' because it does not exist.
At C:\Users\HarmanGrewal\Google Drive\win213\assignment2\assignment2QuestionAnswer1.ps1:13 char:1
+ Get-Content $array1 | export-csv -path "C:\Users\HarmanGrewal\test.tx …
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\Users\HarmanGrewal\192.168.2.10,:String) [Get-Content], ItemNotFound Exception
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
Get-Content : Cannot find path 'C:\Users\HarmanGrewal\192.168.2.14,' because it does not exist.
At C:\Users\HarmanGrewal\Google Drive\win213\assignment2\assignment2QuestionAnswer1.ps1:13 char:1
+ Get-Content $array1 | export-csv -path "C:\Users\HarmanGrewal\test.tx …
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\Users\HarmanGrewal\192.168.2.14,:String) [Get-Content], ItemNotFound Exception
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
Get-Content : Illegal characters in path.
At C:\Users\HarmanGrewal\Google Drive\win213\assignment2\assignment2QuestionAnswer1.ps1:13 char:1
+ Get-Content $array1 | export-csv -path "C:\Users\HarmanGrewal\test.tx …
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (C:\Users\HarmanGrewal\192.168.2.15:String) [Get-Content], ArgumentException
+ FullyQualifiedErrorId : ItemExistsArgumentError,Microsoft.PowerShell.Commands.GetContentCommand
Get-Content : Cannot find path 'C:\Users\HarmanGrewal\192.168.2.15' because it does not exist.
At C:\Users\HarmanGrewal\Google Drive\win213\assignment2\assignment2QuestionAnswer1.ps1:13 char:1
+ Get-Content $array1 | export-csv -path "C:\Users\HarmanGrewal\test.tx …
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\Users\HarmanGrewal\192.168.2.15:String) [Get-Content], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
# Read the 3 IP addresses from the input file into an array.
# .TrimEnd() ensures that a trailing newline, if any, is stripped (syntax requires PSv3+)
$ips = (Get-Content $ipFileName -Delimiter ",").TrimEnd()
# Replace single-digit IP octets with fixed value 2,
# join the resulting IPs with ',' again, and write to an output file.
$ips -replace '\.\d{1}\.', '.2.' -join ',' | Set-Content "C:\Users\HarmanGrewal\test.txt"
As for what you tried:
$array1=#()
$array1 = ...
$array1=#() is pointless, because the next line assigns to $array1 again, which means that its RHS determines the data type of $array1, irrespective of the previous =#() assignment;
if the Get-Content command happens to return a single value, $array1 will be a scalar; you could prevent that by enclosing the Get-Content command in #(...), the array-subexpression operator, but in PSv3+ that is generally not necessary, due to its unified handling of scalars and collections.
foreach($i in $array1) enumerates the array elements themselves, where $i is a by-value copy of each array element.
Instead of using a separate $count variable to access the elements by reference via their index in order to update them, PowerShell allows you to simply recreate the array as a whole:
$array1 = foreach ($el in $array1) { $el -replace "\.\d{1}\.",".2." }
or, more concisely, relying on the -replace operator's support for array-valued LHS values:
$array1 = $array1 -replace "\.\d{1}\.",".2."
As Harsh Jaswal's answer points out, Get-Content $array1 mistakenly passes intended file contents, whereas what Get-Content expects are filename arguments to read contents from.
Since the values to output - in array $array1 - are already in memory, you can simply send them through the pipeline directly.
Export-Csv operates on the properties of an object, and since you're supplying string objects that only have a .Length property, all that will be exported is that property, which is not the intent.
In the case at hand you have to write a text file directly, using Set-Content, based on constructing CSV-format strings in memory.
Note that Set-Content uses the system's legacy "ANSI" code page by default; use -Encoding <encodingName> to change that.
The first thing is if you are using foreach here then there is no need of $count variable. And the second thing is in your last line you are passing collection $array1 to Get-Content. It takes the path as a parameter. So it is trying to get the contents of the file stored at $array1, which is not correct.
Please modify the code as below.
$ipFileName="C:\Users\HarmanGrewal\Google Drive\win213\assignment2\IP.txt"
$array1=#()
$array1=Get-Content $ipFileName -Delimiter ","
#now we have the contents in an array
foreach($i in $array1){
$i = $i -replace "\.\d{1}\.",".2."
$i
}
$array1 | export-csv -path C:\Users\HarmanGrewal\test.txt

Filtering file names with Get-Content

EDIT:
This seems to work. Hope it helps someone:
Get-ChildItem C:\Users\$env:username\Desktop\RD |
Where-Object {$_.Name -match "^[^8]*141.txt"} |
Foreach-Object {$_
Get-Content -Path C:\Users\$env:username\Desktop\RD\$_ |
Out-File C:\Users\$env:username\Desktop\RD\141Master.txt
}
I'm trying to work out filtering file names for a more explicit appending process. So I can do this:
Get-Content C:\erik\*.txt | Out-File C:\erik\whatever.txt
And all the text files append. Then I can do this:
Get-Content C:\erik\*101.txt | Out-File C:\erik\whatever.txt
And all the files with 101 in them append. But when I try something like this:
Get-Content C:\erik\^[^8]*141.txt | Out-File C:\erik\whatever.txt
I get:
Get-Content : An object at the specified path
C:\Users\edarling\Desktop\RD\^[^8]*141.txt does not exist, or has been
filtered by the -Include or -Exclude parameter. At line:1 char:1
+ Get-Content C:\Users\edarling\Desktop\RD\^[^8]*141.txt | Out-File C:\Users\edarl ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (System.String[]:String[]) [Get-Content], Exception
+ FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetContentCommand
I've been trying to pipe Get-ChildItem to Get-Content, but can't quite figure it out. Any suggestions out there?
Thanks
Get-Content supports only globbing, not regular expressions. With the former you can only do things like this:
match all file names that end with 141.txt: *141.txt
match all file names that begin with foo and end with a d or t: foo*[dt]
match all file names that consist of two arbitrary characters followed by the extension .doc: ??.doc
Globbing does not allow you to form an expression to match a name that does not contain particular characters. To Get-Content your expression ^[^8]*141.txt means "a file name that begins with a caret followed by either another caret or the character 8 and ends with 141.txt".
If you need to filter by regular expression you have to use the -match operator:
Get-ChildItem 'C:\some\folder' | ? { $_.Name -match '^[^8]*141\.txt$' }
Note that in regular expressions you need to escape dots if you want to match literal dots (\.). Unescaped dots matche any character except a line feed. You should also anchor your expression on both sides. Otherwise a regular expression ^[^8]*141.txt would match not only abc141.txt, but also something like 141_txt.doc.