Powershell retrieving a variable from a text file - powershell

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]) )

Related

Powershell to present 'Net View' data

happy Easter!
I am trying to write a script in Powershell that takes a list of hosts from a txt (or csv) and then for each does a "net view /all" on it, returning the presented shares in a csv.
I got something working but I need a column to show the host its looking at for each row otherwise I cant map them back.
Attempt 1 returns the data and the host but looks VERY messy and is proving difficult to dissect in Excel:
$InputFile = 'M:\Sources\Temp\net_view_list.txt'
$addresses = get-content $InputFile
foreach($address in $addresses) {
$sharedFolders = (NET.EXE VIEW $address /all)
foreach ($item in $sharedfolders)
{
$str_list = $address + "|" + $item
$obj_list = $str_list | select-object #{Name='Name';Expression={$_}}
$obj_list | export-csv -append M:\sources\temp\netview.csv -notype
}
}
Attempt 2 works better but cant get the hostname listed, plus the comments seem to appear in the "Used as" section (only using for one host to test the theory (didnt work!)):
$command = net view hostname #/all
$netview = $command -split '\n'
$comp = $netview[0].trim().split()[-1]
$result = $netview -match '\w' | foreach {
convertfrom-string $_.trim() -delim '\s{2,}' -propertynames 'Share','Type', 'Used as', 'Comment'
}
$result[0] = $null
$result | format-table 'Share', 'Type', 'Used as', 'Comment' -hidetableheaders
Also neither of these accounts for issues where the host either isn't accessible or has 0 shares.
I have literally spent all day on these - grateful for any guidance!
I will provide the way to get what you want in the your 1st example. The main reason it is not appearing like you are expecting it to is because you are not dealing with a PowerShell object. You are getting the raw output from an external command. What you need to do is take the data and create a PS Custom object then you can use it as you will. Below is the code that you should add after you have the $SharedFolder populated heavily commented to explain what each part is for.
# Create Array to hold PSCustom Object and variable to tell when the DO loop is done
$share_list = #()
$completed = $false
# Loop through each line in the output
for($x=0;$x -lt $sharedFolders.count;$x++){
$next_line = $x + 1
# If the line is a bunch of - then we know the next line contains the 1st share name.
if($sharedFolders[$x] -like "*-------*"){
# Here we will loop until we find the end of the list of shares
do {
# Take the line and split it in to an array. Note when you
# use -split vs variable.split allows you to use regular
# expressions. the '\s+' will consider x number of spaces as one
# the single quotes are important when using regex. Double
# quotes use variable expansion. Single quotes don't
$content = $sharedFolders[$next_line] -split '\s+'
$share_name = $content[0].Trim()
# Create a PS Custom Object. This is a bit over kill for one item
# but shows you how to create a custom Object. Note the Object last
# just one loop thus you create a new one each go round then add it to
# an Array before the loop starts over.
$custom_object = new-object PSObject
$custom_object | add-member -MemberType NoteProperty -name 'Share Name' -Value $share_name
# Add the Custom Object to the Array
$share_list += $custom_object
# This exits the Do loop by setting $completed to true
if($sharedFolders[$next_line] -like "*command completed*"){
$completed = $true
}
# Set to the next line
$next_line++
} until ($completed)
}
}
$share_list

Powershell: how to easily switch between 'Out-String' and 'Get-Content' string variable types?

I want to use the method given in the answer of this question:
PowerShell - Remove all lines of text file until a certain string is found
However I don't get my string from "Get-Content"; I get it from "Out-String". How can I convert my "Out-String" variable into a "Get-Content" format without needing to "Set-Content"/"Get-Content" a temporary file? Or how can I get the same end result without even converting?
It really hurts my brains that a "Get-Member" on the variable from either 'Out-String' or 'Get-Content' returns a TypeName of System.String but you cannot use them the same way...
Here is the simplified code I've been trying to understand - let's use that:
# Let's work with the text from 'Get-Help' output:
$myString = (Get-Help | out-string)
# I only want the text from the "SEE ALSO" section:
$cut = $myString.Where({ $_ -like ("*SEE ALSO*") },'SkipUntil')
$cut # This shows the whole thing!!! :-(
$cut | gm | findstr TypeName # says 'TypeName: System.String'
# Dirty conversion to "Get-Content" format:
Set-Content "tmp.file" -value $cut
$recut = (Get-Content "tmp.file").Where({ $_ -like ("*SEE ALSO*") },'SkipUntil')
$recut # Now this shows what I want, even though the below returns 'TypeName: System.String' as well !!!
(Get-Content "tmp.file") | gm | findstr TypeName
The problem is get-help (with no parameters) or out-string is outputting one multiline string (with windows line endings). I even tried out-string -stream. This is unusual for a powershell command. Get-content would split up the lines for you automatically.
(get-help).count
1
One way to resolve it is to split on the line endings. I'm also skipping blank lines at the end. (This split pattern works with unix/osx text too.)
((get-help) -split '\r?\n').Where({ $_ -like '*SEE ALSO*' },'SkipUntil') | where { $_ }
SEE ALSO:
about_Updatable_Help
Get-Help
Save-Help
Update-Help
Or:
((get-help) -split '\r?\n').Where({ $_ -match 'SEE ALSO' },'SkipUntil').Where{ $_ }
In this case, you do not even need Out-String, but I will stick to your example:
$myString = (Get-Help | Out-String)
$mystring -match "(?ms)^.*(SEE\sALSO.*)$" | Out-Null
$Matches[1]
The key in the regex is (?ms). m enables multi-line search and s enables wildcards to span over multiple lines (in other words: including line breaks). The result of the -match operator is piped to Out-Null to not see it in the terminal. You might want to evaluate it though. If $true, $Matches[1] will contain your desired string.

Performing A String Operation in a -replace Expression

I'm trying to make using of String.Substring() to replace every string with its substring from a certain position. I'm having a hard time figuring out the right syntax for this.
$dirs = Get-ChildItem -Recurse $path | Format-Table -AutoSize -HideTableHeaders -Property #{n='Mode';e={$_.Mode};width=50}, #{n='LastWriteTime';e={$_.LastWriteTime};width=50}, #{n='Length';e={$_.Length};width=50}, #{n='Name';e={$_.FullName -replace "(.:.*)", "*($(str($($_.FullName)).Substring(4)))*"}} | Out-String -Width 40960
I'm referring to the following expression
e={$_.FullName -replace "(.:.*)", "*($(str($($_.FullName)).Substring(4)))*"}}
The substring from the 4th character isn't replacing the Full Name of the path.
The paths in question are longer than 4 characters.
The output is just empty for the Full Name when I run the script.
Can someone please help me out with the syntax
EDIT
The unaltered list of strings (as Get-ChildItem recurses) would be
D:\this\is\where\it\starts
D:\this\is\where\it\starts\dir1\file1
D:\this\is\where\it\starts\dir1\file2
D:\this\is\where\it\starts\dir1\file3
D:\this\is\where\it\starts\dir1\dir2\file1
The $_.FullName will therefore take on the value of each of the strings listed above.
Given an input like D:\this\is or D:\this\is\where, then I'm computing the length of this input (including the delimiter \) and then replacing $_.FullName with a substring beginning from the nth position where n is the length of the input.
If input is D:\this\is, then length is 10.
Expected output is
\where\it\starts
\where\it\starts\dir1\file1
\where\it\starts\dir1\file2
\where\it\starts\dir1\file3
\it\starts\dir1\dir2\file1
If you want to remove a particular prefix from a string you can do so like this:
$prefix = 'D:\this\is'
...
$_.FullName -replace ('^' + [regex]::Escape($prefix))
To remove a prefix of a given length you can do something like this:
$len = 4
...
$_.FullName -replace "^.{$len}"
When having trouble, simplify:
This function will do what you are apparently trying to accomplish:
Function Remove-Parent {
param(
[string]$Path,
[string]$Parent)
$len = $Parent.length
$Path.SubString($Len)
}
The following is not the way you likely would use it but does demonstrate that the function returns the expected results:
#'
D:\this\is\where\it\starts
D:\this\is\where\it\starts\dir1\file1
D:\this\is\where\it\starts\dir1\file2
D:\this\is\where\it\starts\dir1\file3
D:\this\is\where\it\starts\dir1\dir2\file1
'# -split "`n" | ForEach-Object { Remove-Parent $_ 'D:\This\Is' }
# Outputs
\where\it\starts
\where\it\starts\dir1\file1
\where\it\starts\dir1\file2
\where\it\starts\dir1\file3
\where\it\starts\dir1\dir2\file1
Just call the function with the current path ($_.fullname) and the "prefix" you are expecting to remove.
The function above is doing this strictly on 'length' but you could easily adapt it to match the actual string with either a string replace or a regex replace.
Function Remove-Parent {
param(
[string]$Path,
[string]$Parent
)
$remove = [regex]::Escape($Parent)
$Path -replace "^$remove"
}
The output was the same as above.

Retrieve integers from a text file using PowerShell

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

Loading a PowerShell hashtable from a file?

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