I would like to retrieve some numbers from a text file using Windows PowerShell. Assume that I have a text file values.txt that looks like this (I can modify it as I want):
foo=100
bar=-3
foobar=-15
asdf=-4
I would like to add a variable to my PowerShell script called $bar that is equal to the number -3, as the text file says it should be. How do I do this?
Note: The input file looks like part of an *.ini file; for more full-featured support of such files, consider a third-party module such as PSIni.
As Matt suggests in a comment on the question, ConvertFrom-StringData sounds like the right tool:
# Read the key-value pairs stored in file file.txt into [hashtable] $ht
PS> ($ht = Get-Content -Raw -LiteralPath file.txt | ConvertFrom-StringData)
Name Value
---- -----
foo 100
bar -3
foobar -15
asdf -4
# Access a specific value:
PS> $ht.foo # or: $ht['foo']
100
Note:
ConvertFrom-StringData returns a hashtable rather than creating individual variables:
As shown above, you must access the foo input line's value as $ht.foo (or $ht['foo']) rather than $foo, for instance.
To ensure that only a single hashtable is created, Get-Content -Raw (PSv3+) must be used to pass the entire input file as a single string.
ConvertFrom-StringData only ever creates [string] values, so if the input values should be treated as numbers, for instance, manual conversion is required:
# .Clone() is needed to support enumeration and modification in the same loop.
foreach($key in $ht.Keys.Clone()) { $ht.$key = [int] $ht.$key }
Generally, there are more subtleties to consider, such as \ rather than ` serving as the escape character and quotes getting retained as literals - see the docs.
This should work:
$file = "C:\test.txt"
foreach($line in (Get-Content $file)) {
$a = $line.Split("=")
New-Variable -Name $a[0] -Value $a[1]
}
Write-Output ""
Write-Output "Variable Check ::"
Write-Output "foo = $foo"
Write-Output "bar = $bar"
Write-Output "foobar = $foobar"
Write-Output "asdf = $asdf"
You can also put the name/value in different variables if you don't want to use an array, just change the for loop:
$a,$b = $line.Split("=")
New-Variable -Name $a -Value $b
Using Matts suggestion of ConvertFrom-StringData :
$line = $line | ConvertFrom-StringData
New-Variable -name $line.Keys -Value $line.Values
hope this helps
Related
I am trying to collect a value per console, in this case an ip address, and that the suffix of this is self-incrementally and multiplied by line up to 254.
I tried with for, but this create each file text. thats my code.
$ipaddress=$args[0]
New-Item .\direcciones.txt -Force
$filetext= New-Item .\direcciones.txt -Force
for ($i=1, $i -le 254;)
{ Add-Content -Path $filetext -Value $ipaddress.$i }
i expect something like the filetext:
192.168.1.1
192.168.1.2
192.168.1.3
...
192.168.1.254
the autoincrement value that i want is the last octet and the 3 first is the $arg
The key is to enclose $ipaddress.$i in "..."; additionally, you can streamline your code:
1..254 | ForEach-Object { "$ipaddress.$_" } | Set-Content $filetext
Or, more efficiently, but more obscurely:
1..254 -replace '^', "$ipaddress." | Set-Content $filetext
As for what you tried:
(This may just be a posting artifact) for ($i=1, $i -le 254;) creates an infinite loop, because you're missing ; ++$i for incrementing the iterator variable $i.
Even in argument-parsing mode, $ipaddress.$i - in the absence of enclosure in "..." - is interpreted as an expression, meaning that $i is interpreted as the name of a property to access on $ipaddress, which therefore results in $null, so that no data is written to the output file.
Only inside an expandable (double-quoted) string ("...") - i.e. "$ipaddress.$i" in this case - are $ipaddress and $i expanded individually.
So I have a file that looks like :
A=www.google.com
B=www.yahoo.com
Now, I want to convert this text file to a HashTable and read values using keys ie A or B
This is what I have come up with:
$hash = Get-Content .\test.txt
$hash[1].Split('=')[1]
The above script works fine except that I want to use key instead of number
Something like :
$hash['B'].Split('=')[1]
You will need to convert the file data into a hashtable object first. There are several techniques to add data to a hashtable object. The following will convert all lines to a hash table value provided they have the format key=value.
$hash = [ordered]#{}
Get-Content test.txt | Foreach-Object {
$key,$value = ($_ -split '=',2).Trim()
$hash[$key] = $value
}
# Value Retrieval syntax
$hash.A
$hash['A']
If you want to target a specific line in the file, you can do the following:
$hash = [ordered]#{}
$data = Get-Content test.txt
$temp = $data[1] -split '='
$hash[$temp[0]] = $temp[1]
# Value Retrieval Syntax
$hash.B
$hash['B']
You could technically convert the file data with two commands, but the order may vary. I'm not sure if ConvertFrom-StringData is favorable anymore.
$hash = Get-Content test.txt -Raw | ConvertFrom-StringData
# Value Retrieval Syntax
$hash.B
$hash['B']
Output From First Code Snippet:
Get-Content test.txt
A=www.google.com
B=www.yahoo.com
$hash = [ordered]#{}
Get-Content test.txt | Foreach-Object {
$temp = ($_ -split '=').Trim()
$hash[$temp[0]] = $temp[1]
}
$hash
Name Value
---- -----
A www.google.com
B www.yahoo.com
$hash['B']
www.yahoo.com
I am creating the below script to search through and replace data in a set of files. The problem I'm running into is I need to ONLY match if it's the beginning of the line, and I'm not sure how/where would I use regex in the below example (e.g. ^A, ^B) when doing the comparison? I tried putting the caret in front of the name values in the table, but that didn't work...
$lookupTable = #{
'A'='1';
'B'='2'
#etc
}
Get-ChildItem 'c:\windows\system32\dns' -Filter *.dns |
Foreach-Object {
$file = $_
Write-Host "$file"
(Get-Content -Path $file -Raw) | ForEach-Object {
$line = $_
$lookupTable.GetEnumerator() | ForEach-Object {
$line = $line -replace $_.Name, $_.Value
}
$line
} | Set-Content -Path $file
}
The -replace operator accepts Regex. Just $line = $line -replace "^$($_.Name)", "$_.Value".
the way that regex works makes getting a proper "start of line" marker into the regex pattern along with the $VarName a tad iffy. so i broke it out into it's own line and used the -f string format operator to build the regex pattern.
then i used the way that -replace works on an array of strings that one usually gets from Get-Content to work on the whole array at each pass.
note that the strings have lower case items where they otta be replaced, and uppercase items where the item should NOT be replaced. [grin]
$LookUpTable = #{
A = 'Wizbang Shadooby'
Z = '666 is the number of the beast'
}
$LineList = #(
'a sdfq A er Z xcv'
'qwertyuiop A'
'z xcvbnm'
'z A xcvbnm'
'qwertyuiop Z'
)
$LookUpTable.GetEnumerator() |
ForEach-Object {
$Target = '^{0}' -f $_.Name
$LineList = $LineList -replace $Target, $_.Value
}
$LineList
output ...
Wizbang Shadooby sdfq A er Z xcv
qwertyuiop A
666 is the number of the beast xcvbnm
666 is the number of the beast A xcvbnm
qwertyuiop Z
# Here is a complete, working script that beginners can read.
# This thread
# Using regex in a key/value lookup table in powershell?
# https://stackoverflow.com/questions/57277282/using-regex-in-a-key-value-lookup-table-in-powershell
# User-modifiable variables.
# substitutions
# We need to specify what we're looking for (keys).
# We need to specify our substitutions (values).
# Example: Looking for A and substituting 1 in its place.
# Add as many pairs as you like.
# Here I use an array of objects instead of a Hashtable so that I can specify upper- and lowercase matches.
# Use the regular expression caret (^) to match the beginning of a line.
$substitutions = #(
[PSCustomObject]#{ Key = '^A'; Value = '1' },
[PSCustomObject]#{ Key = '^B'; Value = '2' },
[PSCustomObject]#{ Key = '^Sit'; Value = '[Replaced Text]' }, # Example for my Latin placeholder text.
[PSCustomObject]#{ Key = 'nihil'; Value = '[replaced text 2]' }, # Lowercase example.
[PSCustomObject]#{ Key = 'Nihil'; Value = '[Replaced Text 3]' } # Omit comma for the last array item.
)
# Folder where we are looking for files.
$inputFolder = 'C:\Users\Michael\PowerShell\Using regex in a key value lookup table in powershell\input'
# Here I've created some sample files using Latin placeholder text from
# https://lipsum.com/
# Folder where we are saving the modified files.
# This can be the same as the input folder.
# I'm creating this so we can test without corrupting the original files.
$outputFolder = 'C:\Users\Michael\PowerShell\Using regex in a key value lookup table in powershell\output'
#$outputFolder = $inputFolder
# We are only interested in files ending with .dns
$filterString = '*.dns'
# Here is an example for text files.
#$filterString = '*.txt'
# For all files.
#$filterString = '*.*'
# More info.
# https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-childitem?view=powershell-6#parameters
# Search on the page for -Filter
# You won't need to update any variables after this line.
# ===================================================================
# Generate a list of files to look at.
$fileList = Get-ChildItem $inputFolder -Filter $filterString
# Simple example.
# get-content .\apple.dns | % { $_ -replace "sit", "michael" } | set-content "C:\output\apple.dns"
# input file substitutions output
# Set up loops.
# For each file.
#{
# For each key-value pair.
#}
# "For each key-value pair."
# Create a function.
# Pipe in a string.
# Specify a list of substitutions.
# Make the substitutions.
# Output a modified string.
filter find_and_replace ([object[]] $substitutions)
{
# The automatic variable $_ will be a line from the file.
# This comes from the pipeline.
# Copy the input string.
# This avoids modifying a pipeline object.
$myString = $_
# Look at each key-value pair passed to the function.
# In practice, these are the ones we defined at the top of the script.
foreach ($pair in $substitutions)
{
# Modify the strings.
# Update the string after each search.
# case-sensitive -creplace instead of -replace
$myString = $myString -creplace $pair.Key, $pair.Value
}
# Output the final, modified string.
$myString
}
# "For each file."
# main
# Do something with each file.
foreach ($file in $fileList)
{
# Where are we saving the output?
$outputFile = Join-Path -Path $outputFolder -ChildPath $file.Name
# Create a pipeline.
# Pipe strings to our function.
# Let the function modify the strings.
# Save the output to the output folder.
# This mirrors our simple example but with dynamic files and substitutions.
# find_and_replace receives strings from the pipeline and we pass $substitutions into it.
Get-Content $file | find_and_replace $substitutions | Set-Content $outputFile
# The problem with piping files into a pipeline is that
# by the time the pipeline gets to Set-Content,
# we only have modified strings
# and we have no information to create the path for an output file.
# ex [System.IO.FileInfo[]] | [String[]] | [String] | Set-Content ?
#
# Instead, we're in a loop that preserves context.
# And we have the opportunity to create and use the variable $outputFile
# ex foreach ($file in [System.IO.FileInfo[]])
# ex $outputFile = ... $file ...
# ex [String[]] | [String] | Set-Content $outputFile
# Quote
# (Get-Content -Path $file -Raw)
# By omitting -Raw, we get: one string for each line.
# This is instead of getting: one string for the whole file.
# This keeps us from having to use
# the .NET regular expression multiline option (and the subexpression \r?$)
# while matching.
#
# What it is.
# Multiline Mode
# https://learn.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-options#Multiline
#
# How you would get started.
# Miscellaneous Constructs in Regular Expressions
# https://learn.microsoft.com/en-us/dotnet/standard/base-types/miscellaneous-constructs-in-regular-expressions
}
Is there a way to read a text file C:\test.txt and retrieve a particular value?
ie file looks like this:
serverName=serv8496
midasServer=serv8194
I want to set the value of a variable in my script in some way from this file eg:
$MidasServer= (from file midasServer value)
I will not know the line number where the reference is.
Any way to do this?
Yes, read the file, split each line and assign the split result to the Name and Value parameters:
Get-Content file.txt | Foreach-Object{
$var = $_.Split('=')
New-Variable -Name $var[0] -Value $var[1]
}
If that is exactly how your file appears i.e. a list of key value pairs denoted with a equals sign then you should have a look at ConvertFrom-StringData which
converts a string that contains one or more key and value pairs into a hash table. Because each key/value pair must be on a separate line, here-strings are often used as the input format.
So if a text file contained just the data in your example you could do this to create a hashtable
$Path = "C:\temp\test.txt"
$values = Get-Content $Path | Out-String | ConvertFrom-StringData
$values.midasServer
Where the $values.midasServer would have the value serv8194. No need to know where the properties are in respect to the file. Your input file can also have varying leading and trailing space around the equals sign which will give the exact same result.
Depending on your use case you can take that one step farther and create a custom object from that hashtable
New-Object -TypeName pscustomobject -Property $values
If you have at least PowerShell v3 or higher you can simplify the process (assuming you want a custom psobject)
$values = [pscustomobject](Get-Content $Path -Raw | ConvertFrom-StringData)
$values.midasServer
This is an improvement to the Shay Levy's answer. It does the following.
It ignores commented lines and new lines in the file.txt before
start processing the file. So it resolves the error saying that name
could not be created because it is an empty string.
It splits only on the first occurrence of the character "=".
Therefore you can use any characters in the value field.
It performs Trim() operation in order to remove space characters from the beginning and end of the variable/property. Therefore "VARIABLE=VALUE" and "VARIABLE = VALUE" in the file.txt returns the same.
Set the scope of new variables to "Script". Variables created in the
script scope are accessible only within the script file or module
they are created in. Other options are Global, Local and Private.
You can find a variable scope reference here.
Get-Content file.txt | Where-Object {$_.length -gt 0} | Where-Object {!$_.StartsWith("#")} | ForEach-Object {
$var = $_.Split('=',2).Trim()
New-Variable -Scope Script -Name $var[0] -Value $var[1]
}
This was successful for me:
(input file = filename.txt)
[string] $person 'Joe'
[int] $age 50
[datetime] $dob '06/11/1971'
(commands)
Get-Content filename.txt | ForEach-Object {
$invar = $_.Split(" ").Trim()
Invoke-Expression (Invoke-Command -ScriptBlock {
$($invar[0])+$($invar[1])+'='+$($invar[2])
} )
}
If you know there's a "value", that will contain spaces, i.e. "person "Joe B", just join the values, like so...
Replace this:
$($invar[0])+$($invar[1])+'='+$($invar[2])
With this:
$($invar[1])+$($invar[2])+'='+ (-join $($invar[3]),$($invar[4]),$($invar[5]) )
I've got a file containing some data in PowerShell Object Notation:
#{ X = 'x'; Y = 'y' }
I'd like to load this into a variable from the file.
(I figured it out while putting together a repro)
PS> $content = ( Get-Content .\foo.pson | Out-String )
PS> $data = ( Invoke-Expression $content )
Get-Content returns an array with the lines in the file; the Out-String is used to join them together.
Invoke-Expression then runs the script, and the result is captured. This is open to injection attacks, but that's OK in my specific case.
Or, if you prefer your PowerShell terse:
PS> $data = gc .\foo.pson | Out-String | iex
(I can't find a shorter form of Out-String)
I've used ConvertFrom-StringData. If you want to use this approach you'll need to change the way you store key/value pairs with each on its own line and no quotes:
#Contents of test.txt
X = x
Y = y
get-content .\test.txt | ConvertFrom-StringData
Name Value
---- -----
X x
Y y
ConvertFrom-StringData is a built-in cmdlet. I created corresponding ConvertTo-StringData function available here http://poshcode.org/1986
I ran into trouble using ConvertFrom-StringData as #Chad suggested. If you do:
$hash = get-content .\test.txt | ConvertFrom-StringData
I found I had an object array rather than a hash table. In fact, it appears that I had an array of hash tables, each with one entry. I confirmed with a:
$hash.GetType()
It looks like you need to join each line of the slurped input file to ensure that it forms a single string for ConvertFrom..'s use:
$hash = ((get-content .\test.txt) -join '`n') | ConvertFrom-StringData
If you can give this file the extension .ps1, say, data.ps1 then it cannot be simpler than this code:
$data = <path>\data.ps1
Starting from PowerShell 5.0 you have
Import-PowerShellDataFile
Which imports values from a .psd1-file. So the only thing you have to do is rename your file to *.psd1
Official help is here.
This is an older post but, this is sort of a twist on your accepted solution and perhaps slightly more "safe", keep in mind un-trusted files.
From your notes, you have a file that contains a hashtable using Powershell syntax. Given that constraint, you can import it directly:
$HashPath = ".\foo.pson"
# input file contents
$filecontent = Get-Content -Path $HashPath -Raw -ErrorAction Stop
# put the file in a script block
$scriptBlock = [scriptblock]::Create( $filecontent )
#check that the file contains no other Powershell commands
$scriptBlock.CheckRestrictedLanguage( $allowedCommands, $allowedVariables, $true )
#execute it to create the hashtable
$hashtable = ( & $scriptBlock )
Note on the $scriptBlock.CheckRestrictedLanguage you could replace that with
$scriptBlock.CheckRestrictedLanguage([string[]]#(), [string[]]#(), $false)
Use an empty list of strings so we do not allow any Powershell commands. When importing a hashtable, this is exactly what we want. That last one is allowEnvironmentVariables so we restrict that in this example with $false.
Side note, a Powershell module (psd1 file) is just a hashtable so this concept may help you to also pull in script blocks or other things.
Reference: https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.scriptblock.checkrestrictedlanguage?view=powershellsdk-1.1.0