I have the following text in a file named ".connectionInfo":
device name = "WTG001" {
address = "172.28.16.1";
port = 80;
timeout = 3;
rfs = true;
operatingSystem = "vxworks";
}
I have one file as the master (shown above) and wish to create multiple copies in multiple directories with this IP address incrementing by one. In directory "WTG001", I would have .connectionInfo with "172.28.16.1", in directory "WTG002", I would have .connectionInfo with "172.28.16.2", etc.
I have the following Powerscript file and would like to use a counter (IP_Counter) to increment the last digits of the IP address:
$folder="C:\work\Scripting";
$txtFile="C:\work\Scripting\TurbineConfig.txt";
$pattern="\d+.+";
get-content $txtFile | %
{
$IP_Counter = 1
if($_ -match $pattern)
{
Copy-Item -Path C:\work\Scripting\.connectionInfo -Destination "$folder\$_.vxworks";
(Get-Content C:\work\Scripting\.connectionInfo) |
Foreach-Object {$_ -replace '172.28.16.*','172.28.16.$IP_Counter'} |
Out-File "$folder\.connectionInfo";
$IP_Counter++;
...
}
}
I'm having problems with the proper syntax for the "-replace" attribute. I want to replace just the last 3 digits of the IP address with the value $IP_Counter. I think using '172.28.16.*' wildcard finds the last number in the IP but I cannot figure out the precise syntax for the replace string. Please help me identify what '172.28.16.$IP_Counter' should look like.
I can use that same knowledge to replace the "WTG001" text also.
You can use the following -replace expression:
Foreach-Object {$_ -replace '172\.28\.16\.\d+',"172.28.16.$IP_Counter"}
\d+ represents digits only so the semicolon at the end of the line and the quotes won't be replaced. You also need to use double quotes " around the replacement string to interpolate $IP_Counter
I hope there is also some bounds checking for $IP_Counter - in your example it looks like it would loop forever.
Related
this code supposed to look for all demo.txt in the disk and change them from "demo" to "demodemo997182625" and then check if the file has changed or not
$found = 0;
$notfound = 0;
foreach ($file in Get-ChildItem -Path C:\ -Recurse -Filter demo.txt -ErrorAction SilentlyContinue )
{
(Get-Content $file).Replace("demo","demo997182625") | Set-Content $file
$x = (Get-Content $file).contain("demo997182625")
if($x -eq $null){
$found = 1 + $found;
}
else {
$notfound = 1 + $notfound;
}
}
Write-Host "Changed" $found;
Write-Host "Not Changed" $notfound;
A few remarks on your code:
the .Replace() and .Contains() string methods work case-sensitive, so .Replace("demo","demo997182625") won't find and replace "Demo". To have it work case-insensitively, use the -replace operator instead.
updated files can be reprocessed by Get-ChildItem, unless you have that part finish completely first. The easiest way to do that is by enclosing it between brackets
I would only save the file if there was something updated (i.e. the new value was found after -replace), otherwise leave it be
Get-ChildItem returns both FileInfo and DirectoryInfo objects. Since you are interested in changing files only, append the -File switch
best use the FullName property of the found file on the Get- and Set-Content cmdlets instead of the whole FileInfo object
$found = $notfound = 0
# surround the Get-ChildItem line with brackets, so it will finish before iterating on the result
# otherwise, it could reprocess files that were allready updated
foreach ($file in (Get-ChildItem -Path 'C:\' -Recurse -Filter 'demo.txt' -File -ErrorAction SilentlyContinue)) {
# -replace uses regex, so surround the search string with '\b' boundary markers in order to do a whole-word search
$content = (Get-Content -Path $file.FullName -Raw -Force) -replace '\bdemo\b', 'demo997182625'
# test if the content now has the new value (again, use '\b' boundary markers)
if ($content -match '\bdemo997182625\b') {
# save the updated file
$content | Set-Content -Path $file.FullName -Force
$found++
}
else {$notfound++}
}
Write-Host "Changed: $found"
Write-Host "Not Changed: $notfound"
P.S. If your search string contains characters that in regex have special meaning (see table below), you need to escape these with a backslash when using regex operators -replace and -match.
Special Characters in Regex
Char
Description
Meaning
\
Backslash
Used to escape a special character
^
Caret
Beginning of a string
$
Dollar sign
End of a string
.
Period or dot
Matches any single character
|
Vertical bar or pipe symbol
Matches previous OR next character/group
?
Question mark
Match zero or one of the previous
*
Asterisk or star
Match zero, one or more of the previous
+
Plus sign
Match one or more of the previous
( )
Opening and closing parenthesis
Group characters
[ ]
Opening and closing square bracket
Matches a range of characters
{ }
Opening and closing curly brace
Matches a specified number of occurrences of the previous
1
This is part of the code I created:
$NameOrigin = "BP |"
$NameBackup = "Backup"
Get-CimInstance win32_logicaldisk |
ForEach-Object{
if ($_.VolumeName -match "^$NameOrigin(.+)" -and $_.VolumeName -notmatch "^$NameBackup(.+)" ){
$Employee = $Matches[1]
$FSRootOrigin = "{0}{1}" -f $_.DeviceId,'\'
}
}
Basically it checks if there is any drive with the word "BP |" and that does not contain the word "Backup" in the volume label and gets the letter of that drive.
Given that, if you look at the $Employee variable, the idea is that it extracts what is after "|" so I can use it in another step of the script, however for some reason it doesn't work when the drive is named using "|".
When the $NameOrigin variable was "BP -" I was able to extract the information that was after the "-".
Why doesn't it work using "|"?
How can I get around this?
I guess the answer is to escape the pipe because it's regex for "or".
$NameOrigin = "BP \|"
I would like to parse the contents of a variable to display only the path of the .exe without the startup options
$services = Get-WmiObject win32_service | ?{$_.PathName -like '*.exe*'} | select Name, DisplayName, State, Pathname, StartName
foreach ($service in $services) {
$Service.Pathname
}
Result
C:\WINDOWS\system32\svchost.exe -k UnistackSvcGroup
C:\WINDOWS\System32\svchost.exe -k LocalServiceNetworkRestricted -p
Thank you in advance for your feedback
To offer a concise alternative, which also trims enclosing " chars. from command lines with executables containing spaces and recognizes argument-less unquoted executable paths containing spaces without enclosing "...":
((Get-CimInstance win32_service).PathName -match '\.exe\b').ForEach({
if (Test-Path -LiteralPath $_) { # Check if the entire string is an unquoted executable path alone.
$_
} else {
$_ -replace '^(?:"(.+?)"|([^ ]+)).*', '$1$2'
}
})
The above uses:
member-access enumeration to get all .PathName values simply by using property access at the collection level.
the regex-based -match operator to limit the values to those containing the verbatim substring .exe (regex metachar. . must be escaped as \.) at a word boundary (\b).
the .ForEach() array method, for faster processing (than with the ForEach-Object cmdlet) of values already in memory.
The Test-Path is used to check if the entire command line refers to a single executable, possibly with spaces in its path; if so, the entire string ($_) is passed out.
Otherwise, the command line is assumed to include arguments and/or an executable path enclosed in embedded "...".
the regex-based -replace operator with a regex that matches the entire input string and replaces it only with the substring of interest, the executable file path, via capture groups ((...)).
Note: The regex works whether or not the executable path ends in .exe, but given that the command line by definition does contain .exe, simpler solutions are possible, as shown in Santiago's helpful answer and elkenyo's helpful answer, though the latter should be made more robust:
('"C:\Program Files\bar2.exe" -baz' -split '(?<=\.exe\b)')[0].Trim('"')
For an explanation of how the -replace regex and substitution work, see this regex101.com snippet.
A simple demonstration:
$commandLines = 'c:\tools\foo1.exe',
'c:\tools\foo2.exe -bar',
'"C:\Program Files\bar1.exe"',
'"C:\Program Files\bar2.exe" -baz'
$commandLines -replace '^(?:"(.+?)"|([^ ]+)).*', '$1$2'
The above yields:
c:\tools\foo1.exe
c:\tools\foo2.exe
C:\Program Files\bar1.exe
C:\Program Files\bar2.exe
This should work for you;
$services = Get-WmiObject win32_service | ?{$_.PathName -like '*.exe*'} | select Name, DisplayName, State, Pathname, StartName
foreach ($service in $services) {
($Service.PathName -Split ".exe")[0]
# to include .exe:
# "$(($Service.PathName -Split ".exe")[0]).exe"
}
You could use a Select-Object calculated property like so:
$services = Get-WmiObject win32_service | ?{$_.PathName -like '*.exe*'} |
Select-Object Name, DisplayName, State,
#{n='PathName';e={[regex]::Match($_.PathName,'.*\.exe"?').Value}}
I'm using "? at the end of the regex because some Paths are enclosed with ", this will include it.
If you want to exclude the enclosing " you could use (though there is probably a better way):
[regex]::Match($_.PathName,'.*\.exe').Value.TrimStart('"')
Link to test it and explanation: https://regex101.com/r/BFlhVv/1
Then if you do $services.PathName it would show you all the paths without arguments.
I need to parse the output given by the netsh command in powershell.
Below is the command i'm using
Netsh advfirewall show private
It's proving the below output
Private Profile Settings:
----------------------------------------------------------------------
State ON
Firewall Policy BlockInbound,AllowOutbound
LocalFirewallRules N/A (GPO-store only)
LocalConSecRules N/A (GPO-store only)
InboundUserNotification Enable
RemoteManagement Disable
UnicastResponseToMulticast Enable
Logging:
LogAllowedConnections Disable
LogDroppedConnections Disable
FileName %systemroot%\system32\LogFiles\Firewall\pfirewall.log
MaxFileSize 4096
My requirement is to access each and every value in the above output. Something like $result.state / $result.InboundUserNotification
I'm pretty new to PowerShell and i have searched on google about this and wasn't able to find anything.
The obligatory recommendation:
It's always better to find a PowerShell cmdlet to call, so you can take advantage of objects getting output, whose properties you can robustly access - but it sounds like the candidate cmdlet, Get-NetFirewallProfile doesn't work as expected for your use case (domain-joined computers).
Text parsing is always less robust than an object-based solution, though PowerShell offers many powerful features, if needed, notably in this case the switch statement with its -Regex option:
$result = [ordered] #{} # initialize the (ordered) result hashtable.
# Process each line output by netsh, and if it is a line
# containing a property/value-pair, add an entry to the hashtable.
switch -Regex (netsh advfirewall show private) {
'^(.+?) {2,}(.+)' { $result[$Matches.1] = $Matches.2 }
}
# Now you can access $result.State, $result.'Firewall Policy', ...
Note the assumption that the property name and its value are separated by at least two spaces
( {2,}); lines that don't match the property/value pattern are simply skipped.
An alternative is to assume a fixed column width with a single space separating the columns, which does seem to be the case here:
$result = [ordered] #{} # initialize the (ordered) result hashtable.
# Process each line output by netsh, and if it is a line
# containing a property/value-pair, add an entry to the hashtable.
switch -Regex (netsh advfirewall show private) {
'^(.{37}) (.+)' { $result[($Matches.1).TrimEnd()] = $Matches.2 }
}
Note the (...) (parentheses) around $Matches.1, which is unexpectedly required in order to call .TrimEnd() on the value; the parentheses wouldn't be necessary if index syntax ([1]) rather than property syntax .1 were used (that is, $Matches[1].TrimEnd() would work). The problem seems to be specific to property / key names that are numbers. See GitHub issue #14036
If you must parse this output, you can do the following:
$netsh = (Netsh advfirewall show private |
Select-String -Pattern "\s{2,}") -replace '\s{2,}','=' -replace '\\','\\' -join [System.Environment]::NewLine
$result = [pscustomobject](ConvertFrom-StringData $netsh)
The problem with the ConvertFrom-StringData method is the hash table output is not ordered. If order matters, you can just create your hash table by splitting each line into property/value pairs:
$hash = [ordered]#{}
Netsh advfirewall show private | Select-String -Pattern "\s{2,}" |
Foreach-Object {
$key,$value = $_ -split '\s{2,}'
$hash[$key] = $value
}
$result = [pscustomobject]$hash
Here's a similar approach to AdminOfThings answer utilizing ConvertFrom-StringData as well as Foreach-Object's -Begin and -End parameters.
Netsh advfirewall show private |
ForEach-Object -Begin{$ht = [ordered]#{}} {
if($_ -match '\s{10,}')
{
$ht += $_ -replace '\\','\\' -replace '\s{10,}','=' | ConvertFrom-StringData
}
} -End{[PSCustomObject]$ht} -OutVariable result
And a slight variation
Netsh advfirewall show private | Where-Object {$_ -match '\s{10,}'} |
ForEach-Object -Begin{$ht = [ordered]#{}} {
$ht += $_ -replace '\\','\\' -replace '\s{10,}','=' | ConvertFrom-StringData
} -End{[PSCustomObject]$ht} -OutVariable result
I'm trying to update a group of XML based .config files. There is a string in the file that contains a plus sign that I cannot replace with my script:
< SharedPassKey=123456789abcdefghi/JKLM+nopqrst= />
If I include the plus sign, the script does nothing. Since all the configs vary I need to replace the text - cannot just go with new files.
The desired result is that the matching value in the file be replaced with the specified value, but the + symbol is not allowing this. Here is my PS script:
$DIRs = Get-ChildItem -Path "C:\TEST" -Directory
Get-ChildItem $DIRs -File -Recurse -Filter *.config |
ForEach { (Get-Content $_.FullName) |
ForEach { $_ -replace '< SharedPassKey=123456789abcdefghi/JKLM+nopqrst= />','< SharedPassKey=123456789abcdefghi/JKLM.nopqrst= />' } |
Set-Content $_.FullName }
As a test I replaced all the text up to the plus sign and it worked fine, but the plus sign and following were present at the end of the new text as I had left the "+nopqrst" out of the script.
Running on PSv3, FYI.
The -replace operator uses a regular expression for the first postfix argument to define the search pattern. In a regular expression, certain characters have a special meaning. + in particular is a so-called "quantifier" with the meaning "one or more times the preceding (sub)expression". In order to replace literal special characters, you need to escape them.
Fortunately, there's a built-in method for escaping strings:
$_ -replace [RegEx]::Escape('< SharedPassKey=123456789abcdefghi/JKLM+nopqrst= />'),'< SharedPassKey=123456789abcdefghi/JKLM.nopqrst= />'
If you have valid XML data and you want to modify the value of a node attribute in an XML file you may want to consider using a proper XML parser, like this:
$xmlfile = 'C:\path\to\your.config'
[xml]$xml = Get-Content $xmlfile
$attr = $xml.SelectSingleNode('//somenode/#SharedPassKey')
$attr.'#text' = $attr.'#text'.Replace('+', '.')
$xml.Save($xmlfile)
Make the XPath expression for selecting the attribute as specific as it needs to be. That will allow you to replace a + with a . in just that attribute.