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.
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
I am blocked with my code
I will try to explain what I would like to do with it,
my code is to scan the different windows services, to keep in memory only what uses an .exe and then to search among them the ones that the users have full control of.
I would like it to display the service and its rights at the end.
$services = Get-WmiObject win32_service | ?{$_.PathName -like '*.exe*'} | select Name, State, Pathname, StartName | Out-Null
foreach ($service in $services) {
$var = "{0}.exe" -f ($Service.PathName -Split ".exe")[0]
foreach ($right in $var ){
if ( (Get-Acl $var).Access -ccontains "BUILTIN\Utilisateurs FullControl "{
Write-Warning " Exploit detected "
}
}
}
thank you in advance for your feedback
Get-CimInstance Win32_Service |
Where-Object { $_.PathName -like '*.exe*'} |
Select-Object Name, State, Pathname, StartName |
ForEach-Object {
$_.PathName = ($_.PathName -split '(?<=\.exe\b)')[0].Trim('"')
Add-Member -PassThru -InputObject $_ Acl (Get-Acl -LiteralPath $_.PathName)
} |
Where-Object {
$_.Acl.Access.Where({
$_.IdentityReference -ceq 'BUILTIN\Utilisateurs' -and
$_.FileSystemRights -eq 'FullControl'
}, 'First').Count -gt 0
}
Note that I've replaced Get-WmiObject with Get-CimInstance, because the CIM cmdlets superseded the WMI cmdlets in PowerShell v3 (released in September 2012). Therefore, the WMI cmdlets should be avoided, not least because PowerShell (Core) (v6+), where all future effort will go, doesn't even have them anymore. Note that WMI still underlies the CIM cmdlets, however. For more information, see this answer.
The above uses the ForEach-Object call to:
update the .PathName property of each object to contain only the - unquoted - path of the executable with each service.
add an .Acl property to each object via Add-Member, containing the service executable's ACL, obtained via Get-Acl.
The resulting list of objects is then filtered by those whose service-executable ACL contains an entry for identity BUILTIN\Utilisateurs[1] with full control over the executable.
That is, the resulting objects are effectively those for which you meant to issue Write-Warning " Exploit detected "
As for what you tried:
$services = ... | Out-Null by definition captures nothing[2] in variable $services, given that Out-Null's purpose is to suppress output.
While $var = "{0}.exe" -f ($Service.PathName -Split ".exe")[0] does extract the executable path (although .exe should be \.exe\b, for robustness), it may include enclosing double quotes, which should be stripped.
It's unclear where $rights comes from.
You cannot use -ccontains to match across multiple properties of an object, and note that the purpose of the -contains operator and its variants is to test presence of a value in full, in a collection, not to look for a substring in a single string.
[1] It's interesting to see that these identity references are localized; the equivalent on a US-English system would be BUILTIN\Users. Generally, it would be better to obtain a culture-independent representation of this identity, namely its SID, and use that for comparison: $_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value -eq 'S-1-5-32-545'
[2] Loosely speaking, $null; technically, it is the singleton value that PowerShell uses to signal "no output received", [System.Management.Automation.Internal.AutomationNull]::Value.
If I begin by the begining you terminate your first line by | out-null so $services contains nothing.
Then you forgot a ")" in your if.
You should present your code with indentations.
Be carefull :
"{0}.exe" -f ("C:\WINDOWS\system32\msiexec.exe /V"-Split ".exe")[0]
Gives :
C:\WINDOWS\system32\ms.exe
So use a '\' before the '.' :
"{0}.exe" -f ("C:\WINDOWS\system32\msiexec.exe /V"-Split "\.exe")[0]
which gives (regular expression story ?):
C:\WINDOWS\system32\msiexec.exe
So it will give something like that.
$services = Get-WmiObject win32_service | ?{$_.PathName -like '*.exe*'} | select Name, State, Pathname, StartName
foreach ($service in $services) {
$var = "{0}.exe" -f ($Service.PathName -Split "\.exe")[0]
if ((Get-Acl $var.Trim('"') -ErrorAction Stop) -ccontains "BUILTIN\Utilisateurs FullControl "){
Write-Warning " Exploit detected "
}
}
Ok, I am trying to pull only the username out of the localpath properties of the following command:
(Get-WmiObject -ComputerName $ComputerName -Class win32_UserProfile -Filter "localPath like 'c:\\Users\\%'" | sort localpath).localpath.replace('C:\Users\','')
This replaces the c:\Users\ that I don't need. However, at the end of some of the names, there is a .000 or the domain name. For the life of me, I can't not remember how to remove all items after a character. If you know a better way to do this please let me know.
Output:
sfricks
sguess.001
sholcombe
srabanal.000
srainey.OPR.000
ssanders
sspecht.OPR
I can use the split option, however, it takes it from a single line of code to 3+.
$Return = #()
$usernames = (Get-WmiObject -ComputerName $ComputerName -Class win32_UserProfile -Filter "localPath like 'c:\\Users\\%'" -ErrorAction Stop | sort localpath).localpath.replace('C:\Users\','')
foreach ($user in $usernames) {
$Return += $user.Split('.')[0]
}
$Return
I am trying to find the workaround using -replace. I'm not too sure how to do it yet. Just need someone to point me in the right direction.
Where you need to catch variable text for replacement, you're probably better off using regular expressions and the -replace operator instead of the .replace() method. For your purposes, after doing the .replace('C:\Users\',''), use -replace '\..*$',''. The decomposition of the expression is as follows:
\. - Literal dot. The . is normally a special character meaning 'match anything';
the \ escapes it and removes the special meaning.
.* - Match any number of any character. The dot takes on its special character
meaning here, and the star says 'any number of the previous character'
$ - End of string.
There's a good cheat sheet for regular expressions at https://www.debuggex.com/cheatsheet/regex/pcre
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.
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.