1

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

  • 1
    As an aside: The CIM cmdlets (e.g., `Get-CimInstance`) superseded the WMI cmdlets (e.g., `Get-WmiObject`) 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](https://stackoverflow.com/a/54508009/45375). – mklement0 Aug 06 '21 at 16:17

3 Answers3

2

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
mklement0
  • 382,024
  • 64
  • 607
  • 775
1

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"
}
elkenyo
  • 126
  • 4
  • Thank you for your answer, I tried your code without including the .exe it works but when I want to include it, it doesn't work anymore. – Boosted bug Aug 06 '21 at 14:55
  • No worries - strange though, it works for me like that. Try replace the contents of the foreach loop with this line: `"{0}.exe" -f ($Service.PathName -Split ".exe")[0]` – elkenyo Aug 06 '21 at 15:01
  • Nice, but for robustness it should be `"\.exe"`, not `".exe"`. Using a negative lookbehind assertion you could even keep the `.exe` part directly: `('foo.exe -bar' -split '(?<=.exe)')[0]` – mklement0 Aug 06 '21 at 18:20
  • 1
    Nice! I haven't seen this approach before, that's pretty slick - recommend looking at @mklement0's answer instead – elkenyo Aug 07 '21 at 12:53
1

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.

Santiago Squarzon
  • 41,465
  • 5
  • 14
  • 37
  • 1
    Thanks it works, do you think than i can only display the PathName ? @SaSantiago Squarzon – Boosted bug Aug 06 '21 at 14:52
  • @Boostedbug yeah, I updated my answer, first save the result in a variable `$services` then just do `$services.PathName` and it will list all. If the answer was helpful to you please consider [accepting it](https://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work/5235#5235). – Santiago Squarzon Aug 06 '21 at 15:16