If I type out my Powershell select-object expression like below:
$csvdata | Select-Object #{expression={$_.1}; label='first'}
I receive desired output:
first
-
mike
john
But if I store the expression as a string first and then call that string as the expression to select-object:
$tstexp = "#{expression={`$_.1}; label='first'}"
$csvdata | Select-Object $tstexp
The output doesn't evaluate correctly and is used instead as the object name.
#{expression={$_.1}; label='first'}
-------------------------------
Is it possible to pass select-object an expression list as a string?
You can pass it as a [Hashtable]:
$tstexp = #{expression={$_.1}; label='first'}
$csvdata | Select-Object $tstexp
(just remove the quotes).
If it must be a string (I can only imagine that you are reading/generating it from outside your script), then you could evaluate it as a script block:
# Create the string
$tstexp = "#{expression={$_.1}; label='first'}"
# Convert to script block
$tstblock = [scriptblock]::Create($tstexp)
# Execute script block
$tstval = & $tstblock
# $tstval now contains the hashtable
$csvdata | Select-Object $tstval
Edit
If you must use a string, it's easier to use Invoke-Expression as Jeroen Mostert's answer explains (but of course, avoid the string if possible).
You're looking for Invoke-Expression:
$csvdata | select-object (Invoke-Expression $tstexp)
Related
I am trying to solve a somewhat weird problem: I need to replace strings within a raw content by strings from the same content that meet a certain matching criteria. The input data look like this:
apple-beta
apple-alpha_orange-beta
apple-alpha_orange-alpha_cherry-beta
apple-alpha_orange-alpha_kiwi-beta
apple-alpha_orange-alpha_mango-beta
abcd-alpha_efgh-beta
abcd-alpha_efgh-alpha_ijkl-beta
abcd-alpha_efgh-alpha_mnop-beta
The replacment should work as follows: look for all "-beta" strings in the content and delete all according "-alpha" strings (eg because there is "orange-beta" already => all "orange-alpha" should be deleted, because there is "apple-beta" already => all "apple-alpha" should be deleted etc.). The result would look like this:
apple-beta
_orange-beta
__cherry-beta
__kiwi-beta
__mango-beta
abcd-alpha_efgh-beta
abcd-alpha__ijkl-beta
abcd-alpha__mnop-beta
I have tried to achieve this with a number of awkward single replacements and temporary file storages as well as with a while-construction that doesn't work at all:
$whileinput = get-content -raw C:\content-input.txt
while ($whileinput -match "\w+-beta") {
$fullval = $whileinput -match "\w+-beta" -replace "-beta","-alpha"
$whileinput = $whileinput -replace '$fullval',''
}
Any help is very appreciated!
Daniel
I would find all your beta items. Then replace the corresponding alpha items.
$data = Get-Content C:\content-input.txt
$betas = ([regex]::Matches($data,'[^_]*?(?=-beta)').Value -ne '' | Foreach-Object {
[regex]::Escape($_)} ) -join '|'
$data -replace "($betas)-alpha"
Explanation:
[regex]::Matches().Value returns only the matched texts.
[^_]*? lazily matches consecutive characters that are not _. (?=-beta) is a positive lookahead for the text -beta but doesn't include the text in the match.
-ne '' is to filter out blank output.
[regex]::Escape() is not necessarily needed in this case. But it is good practice when your text may have special regex characters that you want to match literally.
$betas contains | delimited items because | is the regex OR. Using () to surround the $betas string allows one of those words to be fully matched before matching -alpha in the replacement.
Get-Content gets the entire contents of a file into a variable, so if anything in your file matches that pattern, it'll loop infinitely (because the contents of the file always match your pattern).
PowerShell is heavily based around the concept of the "pipeline" which you can use in conjunction with the Foreach-Object cmdlet to iterate over each line in a file.
I'm not quite clear on what you want the regexes to do, but I don't think the ones you have will do what you want. Try this.
Get-Content -raw C:\content-input.txt | Foreach-Object {
if($_ -match 'beta$') {
$out+=$_ -replace '\w+-alpha',''
}
}
$out | Out-File .\path-to-output.txt
$_ is the default "pipeline variable" aka the current item in the iteration - in this case the current line. Now at least your loop is working!
Problem
I am trying to modify a file by replacing a very specific substring within a file; however, in this particular instance, the file contains two lines that are nearly identical.
This line is in an AssemblyInfo file and I am trying to replace the value of the AssemblyVersion. See below:
$CurrentVersion = '1.0.*'
$NewVersion = '1.0.7.1'
# Two similar lines:
// [assembly: AssemblyVersion("1.0.*")] # I want to ignore this line
[assembly: AssemblyVersion("1.0.*")] # I want to target this value
I have been trying several different approaches to this, each with varying results.
$Assembly = 'C:\path\to\AssemblyInfo.cs'
$regex = '(?<!\/\/ \[assembly:AssemblyVersion\(")(?<=AssemblyVersion\(")[^"]*'
$regex2 = ('`n\[assembly: AssemblyVersion\("'+$CurrentVersion+'"\)\]')
Attempt 001
(GC $Assembly) |
ForEach-Object { $_.Replace($CurrentVersion, $NewVersion) } |
Set-Content $Assembly
This was an obvious failure. It ends up replacing both instances of '1.0.*'
Attempt 002
GC $Assembly |
Select-String -Pattern '^\[assembly' -AllMatches |
ForEach-Object { $_.Replace($CurrentVersion, $NewVersion) } |
Set-Content $Assembly
This ended with incompatible command issues...
Attempt 003
(GC $Assembly) | ForEAch-Object {
If ( $_ -MATCH $CurrentVersion ) {
ForEach ($Line in $_) {
$_.Replace($CurrentVersion, $NewVersion)
}
}
} |
Set-Content $Assembly
This ended up removing all lines that contained // as the starting characters... which was not what I wanted...
Attempt 004
GC $Assembly |
ForEach-Object {
$_.Replace($regex2, ('[assembly: AssemblyVersion("'+$NewVersion+'")]'))
} |
Set-Content $Assembly
I get an error saying the file is in use... but that didn't make sense as I couldn't find anything using it...
I have tried several other paths as well, most variations of the above 4 in hopes of achieving my goal. Even going so far as to target the line using the regex line provided above and variations of it to try and grab
Question
Using PowerShell, how can I replace only the line/value of the target line (ignoring the line that begins with // ), but still keep all the lines in the file upon saving the contents?
You are trying to use regex patterns but keep using the string method .Replace() which does not support it. You should be using the -replace operator. That would solve part of your issue.
Looks like you only want to replace the line that does not have anything else on it besides the assembly info.
$path = "C:\temp\test.txt"
$newVersion = "1.0.0.1"
$pattern = '^\s*?\[assembly: AssemblyVersion\("(.*)"\)\]'
(Get-Content $path) | ForEach-Object{
if($_ -match $pattern){
# We have found the matching line
'[assembly: AssemblyVersion("{0}")]' -f $newVersion
} else {
# Output line as is
$_
}
} | Set-Content $path
That would be a verbose yet simple to follow way to do what you want. It will only match if the assembly line is at the start of the line with optional spaces.
I would expect that the pattern '^[assembly: AssemblyVersion\("(.*)"\)\]' works just as well since it should appear at the start of the line anyway.
This comes from another answer about almost this exact problem except now there is more that one match possibility. Also you will see my regex pattern isolates the current version in case you need that. If that is the case looked at the linked question.
You can combine this with other options but if you know ahead of time the version you are going to use then the replacement is pretty simple as well.
$newVersion = "1.6.5.6"
(Get-Content $path -Raw) -replace '(?m)^\[assembly: AssemblyVersion\("(.*)"\)\]', ('[assembly: AssemblyFileVersion("{0}")]' -f $newVersion) | Set-Content $path
That reads the file in as one string and performs the replacement as long as the pattern is at the start of the line in the file. (?m) lets us treat the start of line anchor ^ as something that works at the beginning of lines in the text. Not just the start of the whole string.
I need to split a longer string (whole XML file) by </event> to have each event in one element of the created list.
If I do it in a naive way:
$evList = $txtAll.Split("</event>")
I get rubbish. So I ended up doing it this way:
$evList = $txtAll.replace("</event>","|").Split("|")
replacing </event> by | and then splitting at |.
Once in a while this (or other) strange character appears and again I have a mess.
Is there a way to split the long string directly by </event>? How?
When in doubt, read the documentation. If you want to split a string at substrings using the Split() method you need to make the separator an array of strings:
$evList = $txtAll.Split([string[]]"</event>", [StringSplitOptions]::None)
otherwise the separator argument will be interpreted as an array of characters, so you'd be splitting the string at any of the characters in your given separator string.
Or you could use the -split operator, which will allow you to split a string at a regular expression:
$evList = $txtAll -split "</event>"
Make sure the separator string doesn't contain special characters, e.g. by escaping it like this:
$evList = $txtAll -split [regex]::Escape("</event>")
With that said, if you need to extract data from an XML file you'd be far better off using an actual XML parser, e.g. like this:
[xml]$xml = Get-Content 'C:\path\to\your.xml'
$xml.SelectNodes('//event') | Select-Object -Expand InnerText
or like this (using the Select-Xml cmdlet):
Select-Xml -Path 'C:\path\to\your.xml' -XPath '//event' |
Select-Object -Expand Node |
Select-Object -Expand InnerText
I'm using Windows 2012 powershell to scrape values from a 3rd party executable. My goal is to re-arrange and simplify the output of the tool to display only a subset of the content and allow me to collect the data on many devices at once. I have most of the script working, but I'm stuck on a simple task that's so easy in Linux bash.
The 3rd party program is pulling status of a computer device to standard out. I can successfully set the standard out content to a variable. For example:
PS C:\> $status = mycmd.exe device1 --status
PS C:\> $status
The $status variable would return a multi-line list of values as follows:
Device Number: 1
PCIe slot: 3
Firmware Version: 5.1.4
Temperature: 45C
State: Online
In this case, I would like to create a new variable for the firmware version. In Linux I would use something like this (although there are many options available):
Firmware=$(mycmd device1 --status | grep "Firmware" | cut -c 19-24)
In Powershell I can use the Select-String command to can find the "firmware" pattern and set it to a varible as follows:
$Firmware = $Status | select-string -Pattern "Firmware Version"
This gives me the entire Firmware version line. I can't figure out how to substring just the version number from the line as Powershell seems to only want to manipulate the item I'm searching for and not the content next to the pattern. Since this isn't a built in command with an object name, manipulating text seems much more difficult.
I would like the $Firmware variable to equal "5.1.4" or it could be placed into another variable if necessary.
Firmware = ($Status | Select-String -Pattern '\d{1}\.\d{1,2}\.\d{1,2}' -AllMatches | % { $_.Matches } | % { $_.Value }
$Firmware = ($Status | Select-String -Pattern '(?<=Firmware Version:\s+)[\d.]+').Matches.Value
The regular expression here is looking for 1 or more combinations of digits \d and literal dots which are preceded by the Firmware Version: line.
Note that Select-String returns an object, so we use .Matches.Value to get the actual match (which in this case will only be the number).
Using -replace with a multi-line regex:
$Var =
#'
Device Number: 1
PCIe slot: 3
Firmware Version: 5.1.4
Temperature: 45C
State: Online
'#
$Firmware = $var -replace '(?ms).+^Firmware Version:\s+([0-9.]+).+','$1'
$Firmware
5.1.4
This works...I set used a [system.version] to parse the version number (and remove spaces).
$FirmwareLine = $Status | select-string -Pattern "Firmware Version"| select -expand line
[system.version]$firmware=$firmwareLine -split ":" | select -first 1 -skip 1
If you just need the string, you can remove the "cast"
$firmware=$firmwareLine -split ":" | select -first 1 -skip 1
Another option is to replace the colons with '=', converting the output to key=value pairs and then turn that into a hash table (and then to a PS Object if you want) using ConvertFrom-StringData:
$Var =
#'
Device Number: 1
PCIe slot: 3
Firmware Version: 5.1.4
Temperature: 45C
State: Online
'#
$DeviceStatus = New-object PSObject -Property $(ConvertFrom-StringData $var.Replace(':','='))
$DeviceStatus.'Firmware Version'
5.1.4
How can I extract a substring using PowerShell?
I have this string ...
"-----start-------Hello World------end-------"
I have to extract ...
Hello World
What is the best way to do that?
The -match operator tests a regex, combine it with the magic variable $matches to get your result
PS C:\> $x = "----start----Hello World----end----"
PS C:\> $x -match "----start----(?<content>.*)----end----"
True
PS C:\> $matches['content']
Hello World
Whenever in doubt about regex-y things, check out this site: http://www.regular-expressions.info
The Substring method provides us a way to extract a particular string from the original string based on a starting position and length. If only one argument is provided, it is taken to be the starting position, and the remainder of the string is outputted.
PS > "test_string".Substring(0,4)
Test
PS > "test_string".Substring(4)
_stringPS >
But this is easier...
$s = 'Hello World is in here Hello World!'
$p = 'Hello World'
$s -match $p
And finally, to recurse through a directory selecting only the .txt files and searching for occurrence of "Hello World":
dir -rec -filter *.txt | Select-String 'Hello World'
Not sure if this is efficient or not, but strings in PowerShell can be referred to using array index syntax, in a similar fashion to Python.
It's not completely intuitive because of the fact the first letter is referred to by index = 0, but it does:
Allow a second index number that is longer than the string, without generating an error
Extract substrings in reverse
Extract substrings from the end of the string
Here are some examples:
PS > 'Hello World'[0..2]
Yields the result (index values included for clarity - not generated in output):
H [0]
e [1]
l [2]
Which can be made more useful by passing -join '':
PS > 'Hello World'[0..2] -join ''
Hel
There are some interesting effects you can obtain by using different indices:
Forwards
Use a first index value that is less than the second and the substring will be extracted in the forwards direction as you would expect. This time the second index value is far in excess of the string length but there is no error:
PS > 'Hello World'[3..300] -join ''
lo World
Unlike:
PS > 'Hello World'.Substring(3,300)
Exception calling "Substring" with "2" argument(s): "Index and length must refer to a location within
the string.
Backwards
If you supply a second index value that is lower than the first, the string is returned in reverse:
PS > 'Hello World'[4..0] -join ''
olleH
From End
If you use negative numbers you can refer to a position from the end of the string. To extract 'World', the last 5 letters, we use:
PS > 'Hello World'[-5..-1] -join ''
World
PS> $a = "-----start-------Hello World------end-------"
PS> $a.substring(17, 11)
or
PS> $a.Substring($a.IndexOf('H'), 11)
$a.Substring(argument1, argument2) --> Here argument1 = Starting position of the desired alphabet and argument2 = Length of the substring you want as output.
Here 17 is the index of the alphabet 'H' and since we want to Print till Hello World, we provide 11 as the second argument
Building on Matt's answer, here's one that searches across newlines and is easy to modify for your own use
$String="----start----`nHello World`n----end----"
$SearchStart="----start----`n" #Will not be included in results
$SearchEnd="`n----end----" #Will not be included in results
$String -match "(?s)$SearchStart(?<content>.*)$SearchEnd"
$result=$matches['content']
$result
--
NOTE: if you want to run this against a file keep in mind Get-Content returns an array not a single string. You can work around this by doing the following:
$String=[string]::join("`n", (Get-Content $Filename))
other solution
$template="-----start-------{Value:This is a test 123}------end-------"
$text="-----start-------Hello World------end-------"
$text | ConvertFrom-String -TemplateContent $template
Since the string is not complex, no need to add RegEx strings. A simple match will do the trick
$line = "----start----Hello World----end----"
$line -match "Hello World"
$matches[0]
Hello World
$result = $matches[0]
$result
Hello World
I needed to extract a few lines in a log file and this post was helpful in solving my issue, so i thought of adding it here. If someone needs to extract muliple lines, you can use the script to get the index of the a word matching that string (i'm searching for "Root") and extract content in all lines.
$File_content = Get-Content "Path of the text file"
$result = #()
foreach ($val in $File_content){
$Index_No = $val.IndexOf("Root")
$result += $val.substring($Index_No)
}
$result | Select-Object -Unique
Cheers..!