Hi All I'm trying to extract a word from a text output. It should be pretty easy but I've already spent so much time on it. Right now I can extract the line but not just the word.
For example
w32tm /query /status | Select-String -pattern "CMOS"
outputs the line
"Source: Local CMOS Clock"
I only want to extract "Local CMOS Clock"
$var1=w32tm /query /status | Select-String -pattern "CMOS"
$var2=($var1 -split ':')[1] | Out-String
I was able to come up with the above it seems to work I'm not sure if there's a better way, I'm trying to evaluate it through a true/false seem to always pass as true though
For example
if($var2 = "Local CMOS Clock"){
Write-Output "True";
}Else{
Write-Output "False";
}
Always true: even when the condition is wrong
thanks in advance.
I'm not entirely sure of your motives, but here's a cleaner way to get to the answer you're looking for:
Build a PSObject containing the output
The PSObject will contain the output of w32tm. The code works by piping the command output through a loop, at the beginning we make a HashTable and then this is used to build a PowerShell object which is easier to manipulate:
# Pipe the w32tm command through a foreach
# Build a hashtable object containing the keys
# $_ represents each entry in the command output, which is then split by ':'
$w32_obj = w32tm /query /status | ForEach-Object -Begin {$w32_dict = #{}} -Process {
# Ignore blank entries
if ($_ -ne '') {
$fields = $_ -split ': '
# This part sets the elements of the w32_dict.
# Some rows contain more than one colon,
# so we combine all (except 0) the split output strings together using 'join'
$w32_dict[$fields[0]] = $($fields[1..$($fields.Count)] -join ':').Trim()
}
} -End {New-Object psobject -Property $w32_dict}
View the PSObject
Simply run this to display the new PSObject that has been created:
$w32_obj
Now check the 'Source'
Now we can ask for the 'Source' object from $w32_obj by using the dot-notation: $w32_obj.Source:
if($w32_obj.Source -eq "Local CMOS Clock"){
Write-Output "True";
}Else{
Write-Output "False";
}
Further reading
This shows the conversion from HashTable to PSobject and vice-versa
PSCustomObject to Hashtable
here's yet another way to get the False/True from w32tm. my system does not have "cmos" in the output, so i use 'system clock', but the idea will work for your situation.
[bool]((w32tm /query /status) -match 'system clock')
the above returns a $True on my system. that seems a tad more direct than the method you used. [grin]
take care,
lee
Related
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
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.
As part of a bigger script I need to verify a file contents before continuing. However, it's not working when I use | Out-String.
Note this needs to work under powershell v2 unfortunately. The file I am looking at contains the data:
{"somedata":5,"hello":[]}
If I remove | Out-String from the command then it tells me the file matches.
But if I add data to the file, then it still tells me the file matches when it doesn't. If I add | Out-String then it tells me the file doesn't match when it does...
$filecheck = Get-Content ("C:\temp\hello.txt") | Out-String
Write-Host $filecheck
if ($filecheck -eq '{"somedata":5,"hello":[]}') {
Write-Host "file matches"
} else {
Write-Host "doesn't match"
}
As how to fix the issue, see #tukan's answer. Anyway, for learning purposes, let's explore the root cause, namely using the Out-String cmdlet. It actually adds a newline to the string. Like so,
PS C:\temp> $filecheck = Get-Content ("C:\temp\hello.txt") | Out-String
PS C:\temp> write-host $filecheck
{"somedata":5,"hello":[]}
PS C:\temp>
As the data contains a newline, it isn't equal to the string literal used in the if statement. Thus the comparison fails. Remove the Out-String and it works:
PS C:\temp>$filecheck = Get-Content ("C:\temp\hello.txt")
PS C:\temp> $filecheck
{"somedata":5,"hello":[]}
PS C:\temp> $filecheck -eq '{"somedata":5,"hello":[]}'
True
PS C:\temp>
Earlier you noted that Out-String was needed as otherwise adding data would still make the comparison to fail. Why is that? Let's say the data in file is
{"somedata":5,"hello":[]}
{"moredata":1,"foo":bar}
What now happens is that Get-Content will give you an array of strings. The 2nd line consists of {"moredata":1,"foo":bar} plus a newline. Passing such a construct to the comparison will evaluate just the first element, thus a match.
When you pass an array to Out-String, the result is actually a string with data, newline, data and extra newline:
PS C:\temp> $filecheck|out-string
{"somedata":5,"hello":[]}
{"moredata":1,"foo":bar}
PS C:\temp>
This obviously isn't equal to the string literal used in the if statement.
Edit - the script will exit on first match.
Edit2 - you don't need the Else branch if nothing is found print 'failed' at the end suffice.
What about piping into Foreach-Object like this:
(Get-Content 'C:\<our_path>\test.txt') |
Foreach-Object { If ($_ -eq '{"somedata":5,"hello":[]}') {write-host 'matches'; exit}}
write-host 'failed'
I have kept your format of -eq, even thou, I would recommend regex for string/text search.
I'm new to PowerShell and have a script which loops through Active Directory searching for certain computers. I get several variables and then run functions to check things like WMI and registry settings.
In the console, my script runs great and simple Write-Host command prints the data on the screen as I want. I know about Export-Csv when using the pipeline...but I'm not looking to print from the pipeline.
I want to write the variables to a text file, continue the loop, and check the next computer in AD...output the next iteration of the same variables on the next line. Here is my Write-Host:
Write-Host ($computer)","($Speed)","($Regcheck)","($OU)
Output file:
$computer,$Speed,$Regcheck | out-file -filepath C:\temp\scripts\pshell\dump.txt -append -width 200
It gives me the data, but each variable is on its own line. Why? I'd like all the variables on one line with comma separation. Is there a simple way to do this akin to VB writeline? My PowerShell version appears to be 2.0.
Use this:
"$computer, $Speed, $Regcheck" | out-file -filepath C:\temp\scripts\pshell\dump.txt -append -width 200
I usually construct custom objects in these loops, and then add these objects to an array that I can easily manipulate, sort, export to CSV, etc.:
# Construct an out-array to use for data export
$OutArray = #()
# The computer loop you already have
foreach ($server in $serverlist)
{
# Construct an object
$myobj = "" | Select "computer", "Speed", "Regcheck"
# Fill the object
$myobj.computer = $computer
$myobj.speed = $speed
$myobj.regcheck = $regcheck
# Add the object to the out-array
$outarray += $myobj
# Wipe the object just to be sure
$myobj = $null
}
# After the loop, export the array to CSV
$outarray | export-csv "somefile.csv"
You can concatenate an array of values together using PowerShell's `-join' operator. Here is an example:
$FilePath = '{0}\temp\scripts\pshell\dump.txt' -f $env:SystemDrive;
$Computer = 'pc1';
$Speed = 9001;
$RegCheck = $true;
$Computer,$Speed,$RegCheck -join ',' | Out-File -FilePath $FilePath -Append -Width 200;
Output
pc1,9001,True
$computer,$Speed,$Regcheck will create an array, and run out-file ones per variable = they get seperate lines. If you construct a single string using the variables first, it will show up a single line. Like this:
"$computer,$Speed,$Regcheck" | out-file -filepath C:\temp\scripts\pshell\dump.txt -append -width 200
The simple solution is to avoid creating an array before piping to Out-File. Rule #1 of PowerShell is that the comma is a special delimiter, and the default behavior is to create an array. Concatenation is done like this.
$computer + "," + $Speed + "," + $Regcheck | out-file -filepath C:\temp\scripts\pshell\dump.txt -append -width 200
This creates an array of three items.
$computer,$Speed,$Regcheck
FYKJ
100
YES
vs. concatenation of three items separated by commas.
$computer + "," + $Speed + "," + $Regcheck
FYKJ,100,YES
I was lead here in my Google searching. In a show of good faith I have included what I pieced together from parts of this code and other code I've gathered along the way.
# This script is useful if you have attributes or properties that span across several commandlets
# and you wish to export a certain data set but all of the properties you wish to export are not
# included in only one commandlet so you must use more than one to export the data set you want
#
# Created: Joshua Biddle 08/24/2017
# Edited: Joshua Biddle 08/24/2017
#
$A = Get-ADGroupMember "YourGroupName"
# Construct an out-array to use for data export
$Results = #()
foreach ($B in $A)
{
# Construct an object
$myobj = Get-ADuser $B.samAccountName -Properties ScriptPath,Office
# Fill the object
$Properties = #{
samAccountName = $myobj.samAccountName
Name = $myobj.Name
Office = $myobj.Office
ScriptPath = $myobj.ScriptPath
}
# Add the object to the out-array
$Results += New-Object psobject -Property $Properties
# Wipe the object just to be sure
$myobj = $null
}
# After the loop, export the array to CSV
$Results | Select "samAccountName", "Name", "Office", "ScriptPath" | Export-CSV "C:\Temp\YourData.csv"
Cheers
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]) )