Powershell - Matches [1] don't work with "|" - windows

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 \|"

Related

Powershell - Stop if there is more than one match

This is part of the code I created:
$NameBackup = "Backup"
$DestinationDrive = ""
Get-CimInstance win32_logicaldisk |
ForEach-Object{
if ($_.VolumeName -match "$NameBackup"){
$DestinationDrive = "{0}{1}" -f $_.DeviceId,'\'
}
}
Basically it checks if there is any drive with the word "Backup" in the volume label and gets the letter of that drive.
Given this, I would like to create a code to stop the script and display an error message if there is more than one -match with the $NameBackup variable.
How can I do this?
You could use -Filter in this case, then check if the .Count property of the returned query is greater than 1, and if it is, use throw to halt your script. It may be also worth adding a new condition checking if $cim is populated.
$NameBackup = "Backup"
$cim = Get-CimInstance Win32_LogicalDisk -Filter "VolumeName LIKE '%$NameBackup%'"
if($cim.Count -gt 1) {
throw "More than one Volume Detected"
}
$DestinationDrive = $cim.DeviceID + '\'

parse the contents of a variable powershell

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.

using -replace to remove a string with special characters from cells in a csv

I have a CSV file like:
"localpath"
"C:\Users\calabresel"
"C:\Users\goslinep"
"C:\Users\deangelisr"
"C:\Users\bannont"
"C:\Users\goodwind"
I am looking for a way to isolate just the username from each field. I will then query the AD to determine if each user is disabled or enabled. I haven't been able to figure out how to get just the last piece though. My idea was to use -replace to replace the identical string with null like this:
$txt = import-csv paths1.csv | % {$_.localpath = $_.localpath -replace "C:\Users\", ""}
That came back with invalid regular expression pattern errors though which I assumed was a result of the target string containing special characters (the backslashes). I then started looking for a way to get powershell to take the \ literally instead. That lead me to try this:
$txt = import-csv paths1.csv | % {$_.localpath = $_.localpath -replace [Regex]::Escape("C:\\Users\\"), ""}
and this
$txt = import-csv paths1.csv | % {$_.localpath = $_.localpath -replace "C:\\Users\\", ""}
both of those methods stop the invalid regular expression errors and just return me a fresh line without complaining. however when I print the $txt variable it is empty...
I'm certain I am approaching this problem from the wrong angle and/or with improper syntax but I could use some guidance as I just started working with powershell a week ago.
any help provided would be greatly appreciated.
The following will import the CSV file and then get the leaf of the path. I.e the user name.
$txt = Import-Csv paths1.csv | ForEach-Object { Split-Path $_.localpath -leaf }
If you still want to use your replace method, just take out the $_.localpath = part and it should work.
$txt = Import-Csv C:\##Scatch\test.csv | % { $_.localpath -replace "C:\\Users\\", ""}
The reason why you aren't getting anything back into $txt is that you update a property of $_ but don't return $_.
Assuming that you want to use the regex rather than Split-Path
$txt = import-csv C:\temp\test.csv | % {
$_.localpath = $_.localpath -replace "C:\\Users\\", ""
$_
}
Or
$txt = import-csv C:\temp\test.csv | % {
$_.localpath -replace "C:\\Users\\", ""
}
other solution
Get-Content "C:\temp\test.txt" | select #{N="Value";E={$_.split('\')[-1].replace('"', '')}} -Skip 1

Powershell: find/replace string using wildcard - syntax difficulty

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.

Breaking foreach loop in PowerShell v2

I'm having trouble terminating a foreach-object loop in PowerShell v2. For a rough idea of the task I'm trying to accomplish, here's the pseudo-code:
Read lists of host machines from a text file
For each host in the text file get Win32_Product (filtered against an exclusion list),
convert output to html and save.
The reason for the script is that I've amassed a text file listing all applications included on standard client images, and would like to periodically scan hosts from another text file to see if there are any unauthorized, sketchy or otherwise unnecessary applications on the host machines.
The code does work in a rough sense, but the main issue I'm having is that the script will not terminate without manual intervention. I guess the component I'm missing here is to run the loop until some condition exists (ie. first line in the host file is encountered for the second time), then terminates the script. Although this is the method I've envisioned, I am always open to other logic, especially if its more efficient.
Here's the actual code:
Get-Content c:\path\to\testhostlist.txt | Foreach-Object {
Get-WmiObject Win32_Product |
Where-Object { $_.Name -f "'C:\path\to\testauthapplist.txt'" |
ConvertTo-Html name,vendor,version -title $name -body "<H2>Unauthorized Applications.</H2>"}} |
Set-Content c:\path\to\unauthapplisttest.html
I don't see how the first line of the host file (I infer you mean testhostlist.tx) would ever be encountered a second time, since you're only listing it once. This doesn't even seem to be an infinite loop that would need an exit condition. Foreach-Object doesn't repeat indefinitely.
It seems to me that the problem is not that the loop doesn't exit without a condition, it's that the syntax is invalid.
Where-Object filters the pipeline by passing only objects that meet a certain condition, but the scriptblock that follows doesn't perform a boolean test.
In fact, the content of the scriptblock doesn't appear valid in and of itself. -f is the format operator, and takes a format string as the left operand, but $_.Name is not a format string.
I'm going to take a guess here, based on your description, that the idea is to filter the results of Get-WmiObject Win32_Product for objects whose Name property isn't listed in testauthapplist.txt (I take it that's the "exclusion list" you're referring to). If so, this is the correct syntax:
Get-Content c:\path\to\testhostlist.txt | %{
Get-WmiObject Win32_Product | ?{
(Get-Content 'C:\path\to\testauthapplist.txt') -notcontains $_.Name
} | ConvertTo-Html name,vendor,version -title $name -body "<H2>Unauthorized Applications.</H2>"
} | Set-Content c:\path\to\unauthapplisttest.html
(Note that %{} and ?{} are just abbreviations for Foreach-Object and Where-Object, respectively.)
If i understood you correctly you are trying to stop your Script completely? If so did you try Break?
If you only want to skip a loop use continue
$hostlist = Get-Content c:\path\to\testhostlist.txt
$a = #()
Foreach($item in $hostlist)
{
$a += "<style>"
$a += "BODY{background-color:gray;}"
$a += "TABLE{margin: auto;border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}"
$a += "TH{border-width: 1px;padding: 4px;border-style: solid;border-color: black;background-color:yellow}"
$a += "TD{border-width: 1px;padding: 4px;border-style: solid;border-color: black;background-color:white}"
$a += "h2{color:#fff;}"
$a += "</style>"
Get-WmiObject Win32_Product | select name,vendor,version | sort name | ConvertTo-Html -head $a -body "<Center><H2>Unauthorized Applications.</H2></Center>" | Out-File c:\path\to\$item"-applist.html"
}

Resources