i have listed all the processes with handle.exe that access one directory.
function Get-FileHandle ($HPath){
$handles = handle $HPath
}
the output seems like:
Nthandle v4.11 - Handle viewer Copyright (C) 1997-2017 Mark
Russinovich Sysinternals - www.sysinternals.com
jabra-direct.exe pid: 12716 type: File 838:
C:\Windows\System32\drivers\etc
Creative Cloud.exe pid: 4280 type: File 9D0:
C:\Windows\System32\drivers\etc
Adobe CEF Helper.exe pid: 12916 type: File 494:
C:\Windows\System32\drivers\etc
brave.exe pid: 2920 type: File 690:
C:\Windows\System32\drivers\etc
brave.exe pid: 13828 type: File 344:
C:\Windows\System32\drivers\etc
Now I am trying to list only the processnames without all the other values.
tha code i have, is:
foreach ($handle in $gethandle) {
$handle.Split(" ") | ?{$_ -like "*exe"}
}
The output is:
jabra-direct.exe
Cloud.exe
Helper.exe
brave.exe
brave.exe
everything fine untill the processname contains a space. It should be creative cloud.exe and not cloud.exe.
How can i make it work ? sothat creative cloud.exe will be shown and not only cloud.exe ?
Here is a way using the Select-String cmdlet.
$handles = handle $HPath # assuming handle.exe can be found via PATH env var
$selected = $handles | Select-String -Pattern '.*?(?= +pid:)'
$processNames = $selected.Matches.Value # array of process names
The Select-String line extracts the process names from the output using a regular expression:
.*? - everything up to the following pattern (as little as possible to trim trailing whitespace)
(?= - starts a positive lookahead pattern
+ - one ore more space characters
pid: - literal "pid:"
) - ends the positive lookahead pattern
The positive lookahead makes sure that we only find sub strings followed by " pid:", without including " pid:" in the result.
The expression $selected.Matches.Value is a shortcut for:
$processNames = #()
foreach( $sel in $selected ) {
foreach( $match in $sel.Matches ) {
$processNames += $match.Value
}
}
When PowerShell can't find a property on an object that is an array, it automatically searches each array member for that property and returns an array of all values that is has found. This is called member enumeration.
Try the following code and adapt if necessary.
Regex is (.*)(?=pid:)pid:\s(\d+)\stype:\s([^\d]+)\s([\dABCDEF]+):\s(.*) that combine a positive lookahead and capturing groups. Then just use the auto property $Matches to extract capturing group values.
$output = #"
Nthandle v4.11 - Handle viewer Copyright (C) 1997-2017 Mark Russinovich Sysinternals - www.sysinternals.com
jabra-direct.exe pid: 12716 type: File 838: C:\Windows\System32\drivers\etc
Creative Cloud.exe pid: 4280 type: File 9D0: C:\Windows\System32\drivers\etc
Adobe CEF Helper.exe pid: 12916 type: File 494: C:\Windows\System32\drivers\etc
brave.exe pid: 2920 type: File 690: C:\Windows\System32\drivers\etc
brave.exe pid: 13828 type: File 344: C:\Windows\System32\drivers\etc
"#
$output = $output.Split("`r`n")
$output | ForEach-Object {
if ($_ -match "(.*)(?=pid:)pid:\s(\d+)\stype:\s([^\d]+)\s([\dABCDEF]+):\s(.*)") {
$props = [ordered]#{
Name = $Matches[1]
PID = $Matches[2]
Type = $Matches[3]
Handle = $Matches[4]
Path = $Matches[5]
}
New-Object -TypeName PSObject -Property $props
}
} | Format-Table -AutoSize
Related
i am completely new to Powershell and I am trying to create a script to extract the timestamp for the string found in a log file. There are log files everyday and it should select the latest file. It then looks for a string in that file. Now, where it finds the string it should also return a timestamp where the string is located. I am only able to complete the first part and struggling with the 2nd bit. My script is in no way optimal but it is a start. Any help is appreciated.
Set-Location -Path "O:\xyz\0502251\logs"
$latest = (Get-ChildItem -Path $dir -Filter 'async_SPARKLE_ONITE_*.log' | Sort-Object CreationDate -Descending | Select-Object -last 1).fullname
$pattern = "Successfulyl finished running psp_dba_maint_4"
$search = (Get-Content $latest | Select-String -Pattern "psp_dba_maint_4" | Select-Object -last 1)
if($search)
{
"The overnight completed at:"
}
else
{
"Do something"
}
This looks for the latest file and extracts the string. Now Just want to know at what time this log was generated.
The log is:
Connection 1
Server message: 2022-10-15 23:56:07:020
Message number: 0, Severity 10, State 1, Line 126
Procedure 'psp_dba_locklogin'
Message String: -----> login SPRK_SVC_IRISS logged in at 23:55:45 running SRI.exe:AWAITING COMMAND on rptSPARKLE from host da-pvrep02 is still active
Connection 1
Server message: 2022-10-15 23:56:07:020
Message number: 0, Severity 10, State 1, Line 126
Procedure 'psp_dba_locklogin'
Message String: -----> login SPRK_SVC_IRISS logged in at 23:55:45 running SRI.exe:AWAITING COMMAND on rptSPARKLE from host da-pvrep05 is still active
Connection 1
Server message: 2022-10-15 23:56:07:020
Message number: 0, Severity 10, State 1, Line 126
Procedure 'psp_dba_locklogin'
Message String: -----> login SPRK_SVC_IRISS logged in at 23:55:39 running SRI.exe:SELECT on rptSPARKLE from host da-pvrep02 is still active
Connection 1
Server message: 2022-10-15 23:56:07:020
Message number: 0, Severity 10, State 1, Line 126
Procedure 'psp_dba_locklogin'
Message String: -----> login SPARKLE_CAI logged in at 23:55:37 running SPARKLE ESB (pooled):AWAITING COMMAND on A_uatSPK from host DA-UVESB01 is still active
Connection 1
Server message: 2022-10-15 23:56:07:020
Message number: 0, Severity 10, State 1, Line 355
Procedure 'psp_dba_dbrefresh'
Message String: Setting PrivShield on SPRKSZCBS OUT OF maintenance mode
Connection 1
Server message: 2022-10-15 23:56:07:022
Message number: 0, Severity 10, State 1, Line 362
Procedure 'psp_dba_dbrefresh'
Message String:
Connection 1
Server message: 2022-10-15 23:56:07:022
Message number: 0, Severity 10, State 1, Line 364
Procedure 'psp_dba_dbrefresh'
Message String: - Refresh 2 complete
Connection 1
Server message: 2022-10-15 23:56:07:028
Message number: 0, Severity 10, State 1, Line 42
Procedure 'psp_dba_maint_4'
Message String: Successfulyl finished running psp_dba_maint_4
This has different values in files generated everyday. So it should look for the latest file and return the result as The overnight completed at: 23:56.
Here is a variant using the switch statement to efficiently process the file line-by-line and test each line to match given RegEx patterns:
$found = switch -File $latest -RegEx {
'\d\d:\d\d' { $timestamp = $matches[0] }
'Successfulyl finished running psp_dba_maint_4' { $true; break }
}
if( $found ) {
"The overnight completed at: $timestamp"
}
else {
"Not found"
}
$found = switch ... assigns the output of the switch statement to the variable. The output will be $true if the pattern has been found.
\d\d:\d\d searches for the time (first occurence of a pair of two digits separated by :) within the current line. The automatic variable $matches[0] then gives the matched value, similar to the RegEx -match operator. For a detailed explanation of the RegEx pattern and the ability to experiment with it, see this Regex101.com page.
Although there is no $false output of the switch statement, an empty output evaluates to $false in a boolean context (in this case the if statement), so if the pattern isn't found, the else branch will be entered.
There is a typo in Successfulyl which I just copied from your example ;)
To complement zett42's helpful switch-based answer:
A Select-String solution, as you attempted, is possible, via the -Context parameter, which allows you to capture the lines surrounding a matching line as well:
$time =
Select-String -LiteralPath $latest -Pattern 'psp_dba_maint_4' -Context 3 |
Select-Object -Last 1 |
ForEach-Object {
# Extract and output the 'HH:mm' part of the timestamp from
# the 3rd line above the match (the first element in the pre-context array)
$_.Context.PreContext[0] -replace '^.+ (..:..).+$', '$1'
}
if ($time) {
"The overnight completed at: $time"
} else {
'Do something'
}
Note:
It is much more efficient to pass the file path of the target file to Select-String, via -LiteralPath, than it is to pass the file's content, line by line, via Get-Content.
If you had assigned the whole file-info object (System.IO.FileInfo), as emitted by Get-ChildItem, to $latest, you could alternatively have provided it via the pipeline: $latest | Select-String ...
The type of the match-information objects that Select-String emits is Microsoft.PowerShell.Commands.MatchInfo.
The regex-based -replace operator is used to extract (part of) the time-of-day string from the context line.
For an explanation of the above regex (and substitution, where $1 refers to the first and only capture group, (...)), see this regex101.com page.
As you need to get the Time information only related to the procedure psp_dba_maint_4 you can do:
#Remove empty lines, join strings and split string at "connection 1" = you keep the related information together. Next parse the array of strings for the string which matches "psp_dba_maint_4" and after that extract dateTime
$null = (((Get-Content $latest | ?{$_}) -join $null) -split "connection 1" | ?{$_ -match "psp_dba_maint_4"}) -match '\d{2}:\d{2}:\d{2}'
$Search = $matches[0]
if($search){
"The overnight completed at: $search"
}
else{
"Do something"
}
Alternatively you could parse the log an create objects, e.g.:
$obj = #(
Get-Content $latest | ?{$_} | %{
#If string is connection 1 initialize hashtable/create object
If ($_ -match 'connection 1'){
If ($attrsHT){
new-object -typename psobject -Property $attrsht
}
$attrsHt = #{}
}
Else {
#replace Procedure with Procedure, replace ": " with | and split at |
$split = (($_ -replace "Procedure","Procedure:") -replace ": ","|") -split "\|"
#add key and value to hashtable
$attrsht.add($split[0],$split[1])
}
}
)
gives you the array $obj containing the loginformation:
$obj[0] | fl *
Procedure : 'psp_dba_locklogin'
Message String : -----> login SPRK_SVC_IRISS logged in at 23:55:45 running SRI.exe:AWAITING COMMAND on
rptSPARKLE from host da-pvrep02 is still active
Server message : 2022-10-15 23:56:07:020
Message number : 0, Severity 10, State 1, Line 126
Here's another solution to add to the rest.
if ($found = Get-Content -Path $latest -ReadCount 7 | %{
if ($_ -match 'Setting PrivShield on SPRKSZCBS OUT OF maintenance mode')
{
[regex]::Match($_,'\d\d:\d\d').Value
}
}
) { "The overnight completed at: $found" }
else { 'not found' }
On another note, $latest contains the oldest file and not the newest. Specifying -Descending in your Sort-Object brings the most current files to the top. So, when you select the last one via -Last 1, that is the oldest file and not the newest.
I am trying to store a text file string which has a beginning and end that make it a substring of the original text file. I am new to Powershell so my methods are simple/crude. Basically my approach has been:
Roughly get what I want from the start of the string
Worry about trimming off what I don't want later
My minimum reproducible example is as follows:
# selectStringTest.ps
$inputFile = Get-Content -Path "C:\test\test3\Copy of 31832_226140__0001-00006.txt"
# selected text string needs to span from $refName up to $boundaryName
[string]$refName = "001 BARTLETT"
[string]$boundaryName = "001 BEECH"
# a rough estimate of the text file lines required
[int]$lines = 200
if (Select-String -InputObject $inputFile -pattern $refName) {
Write-Host "Selected shortened string found!"
# this selects the start of required string but with extra text
[string]$newFileStart = $inputFile | Select-String $refName -CaseSensitive -SimpleMatch -Context 0, $lines
}
else {
Write-Host "Selected string NOT FOUND."
}
# tidy up the start of the string by removing rubbish
$newFileStart = $newFileStart.TrimStart('> ')
# this is the kind of thing I want but it doesn't work
$newFileStart = $newFileStart - $newFileStart.StartsWith($boundaryName)
$newFileStart | Out-File tempOutputFile
As it is: the output begins correctly but I cannot remove text including and after $boundaryName
The original text file is OCR generated (Optical Character Recognition) So it is unevenly formatted. There are newlines in odd places. So I have limited options when it comes to delimiting.
I am not sure my if (Select-String -InputObject $inputFile -pattern $refName)is valid. It appears to work correctly. The general design seems crude. In that I am guessing how many lines I will need. And finally I have tried various methods of trimming the string from $boundaryName without success. For this:
string.split() not practical
replacing spaces with newlines in an array & looping through to elements of $boundaryName is possible but I don't know how to terminate the array at this point before returning it to string.
Any suggestions would be appreciated.
Abbreviated content of x2 200 listings single Copy of 31832_226140__0001-00006.txt file is:
Beginning of text file
________________
BARTLETT-BEDGGOOD
PENCARROW COMPOSITE ROLL
PAGE 6
PAGE 7
PENCARROW COMPOSITE ROLL
BEECH-BEST
www.
.......................
001 BARTLETT. Lois Elizabeth
Middle of text file
............. 15 St Ronans Av. Lower Hutt Marned 200 BEDGGOOD. Percy Lloyd
............15 St Ronans Av, Lower Mutt. Coachbuild
001 BEECH, Margaret ..........
End of text file
..............312 Munita Rood Eastbourne, Civil Eng 200 BEST, Dons Amy .........
..........50 Man Street, Wamuomata, Marned
SO NON
To use a regex across newlines, the file needs to be read as a single string. Get-Content -Raw will do that. This assumes that you do not want the lines containing refName and boundaryName included in the output
$c = Get-Content -Path '.\beech.txt' -Raw
$refName = "001 BARTLETT"
$boundaryName = "001 BEECH"
if ($c -match "(?smi).*$refName.*?`r`n(.*)$boundaryName.*?`r`n.*") {
$result = $Matches[1]
}
$result
More information at https://stackoverflow.com/a/12573413/447901
How close does this come to what you want?
function Process-File {
param (
[Parameter(Mandatory = $true, Position = 0)]
[string]$HeadText,
[Parameter(Mandatory = $true, Position = 1)]
[string]$TailText,
[Parameter(ValueFromPipeline)]
$File
)
Process {
$Inside = $false;
switch -Regex -File $File.FullName {
#'^\s*$' { continue }
"(?i)^\s*$TailText(?<Tail>.*)`$" { $Matches.Tail; $Inside = $false }
'^(?<Line>.+)$' { if($Inside) { $Matches.Line } }
"(?i)^\s*$HeadText(?<Head>.*)`$" { $Matches.Head; $Inside = $true }
default { continue }
}
}
}
$File = 'Copy of 31832_226140__0001-00006.txt'
#$Path = $PSScriptRoot
$Path = 'C:\test\test3'
$Result = Get-ChildItem -Path "$Path\$File" | Process-File '001 BARTLETT' '001 BEECH'
$Result | Out-File -FilePath "$Path\SpanText.txt"
This is the output:
. Lois Elizabeth
............. 15 St Ronans Av. Lower Hutt Marned 200 BEDGGOOD. Percy Lloyd
............15 St Ronans Av, Lower Mutt. Coachbuild
, Margaret ..........
This code works. I just want to see how much faster someone can make it work.
Backup your Windows 10 batch file in case something goes wrong. Find all instances of string {LINE2 1-9999} and replace with {LINE2 "line number the code is on"}. Overwrite, encoding as ASCII.
If _61.bat is:
TITLE %TIME% NO "%zmyapps1%\*.*" ARCHIVE ATTRIBUTE LINE2 1243
TITLE %TIME% DOC/SET YQJ8 LINE2 1887
SET ztitle=%TIME%: WINFOLD LINE2 2557
TITLE %TIME% _*.* IN WINFOLD LINE2 2597
TITLE %TIME% %%ZDATE1%% YQJ25 LINE2 3672
TITLE %TIME% FINISHED. PRESS ANY KEY TO SHUTDOWN ... LINE2 4922
Results:
TITLE %TIME% NO "%zmyapps1%\*.*" ARCHIVE ATTRIBUTE LINE2 1
TITLE %TIME% DOC/SET YQJ8 LINE2 2
SET ztitle=%TIME%: WINFOLD LINE2 3
TITLE %TIME% _*.* IN WINFOLD LINE2 4
TITLE %TIME% %%ZDATE1%% YQJ25 LINE2 5
TITLE %TIME% FINISHED. PRESS ANY KEY TO SHUTDOWN ... LINE2 6
Code:
Copy-Item $env:windir\_61.bat -d $env:temp\_61.bat
(gc $env:windir\_61.bat) | foreach -Begin {$lc = 1} -Process {
$_ -replace "LINE2 \d*", "LINE2 $lc";
$lc += 1
} | Out-File -Encoding Ascii $env:windir\_61.bat
I expect this to take less than 984 milliseconds. It takes 984 milliseconds. Can you think of anything to speed it up?
The key to better performance in PowerShell code (short of embedding C# code compiled on demand with Add-Type, which may or may not help) is to:
avoid use of cmdlets and the pipeline in general,
especially invocation of a script block ({...}) for each pipeline input object, such as with ForEach-Object and Where-Object
However, it isn't the pipeline per se that is to blame, it is the current inefficient implementation of these cmdlets - see GitHub issue #10982 - and there is a workaround that noticeably improves pipeline performance:
# Faster alternative to:
# 1..10 | ForEach-Object { $_ * 10 }
1..10 | . { process { $_ * 10 } }
# Faster alternative to:
# 1..10 | Where-Object { $_ -gt 5 }
1..10 | . { process { if ($_ -gt 5) { $_ } } }
avoiding the pipeline requires direct use of the .NET framework types as an alternative to cmdlets.
if feasible, use switch statements for array or line-by-line file processing - switch statements generally outperform foreach loops.
To be clear: The pipeline and cmdlets offer clear benefits, so avoiding them should only be done if optimizing performance is a must.
In your case, the following code, which combines the switch statement with direct use of the .NET framework for file I/O seems to offer the best performance - note that the input file is read into memory as a whole, as an array of lines, and a copy of that array with the modified lines is created before it is written back to the input file:
$file = "$env:temp\_61.bat" # must be a *full* path.
$lc = 0
$updatedLines = & { switch -Regex -File $file {
'^(.*? LINE2 )\d+(.*)$' { $Matches[1] + ++$lc + $Matches[2] }
default { ++$lc; $_ } # pass non-matching lines through
} }
[IO.File]::WriteAllLines($file, $updatedLines, [Text.Encoding]::ASCII)
Note:
Enclosing the switch statement in & { ... } is an obscure performance optimization explained in this answer.
If case-sensitive matching is sufficient, as suggested by the sample input, you can improve performance a little more by adding the -CaseSensitive option to the switch command.
In my tests (see below), this provided a more than 4-fold performance improvement in Windows PowerShell relative to your command.
Here's a performance comparison via the Time-Command function:
The commands compared are:
The switch command from above.
A slightly streamlined version of your own command.
A PowerShell Core v6.1+ alternative that uses the -replace operator with the array of lines as the LHS and a scriptblock as the replacement expression.
Instead of a 6-line sample file, a 6,000-line file is used.
100 runs are being averaged.
It's easy to adjust these parameters.
# Sample file content (6 lines)
$fileContent = #'
TITLE %TIME% NO "%zmyapps1%\*.*" ARCHIVE ATTRIBUTE LINE2 1243
TITLE %TIME% DOC/SET YQJ8 LINE2 1887
SET ztitle=%TIME%: WINFOLD LINE2 2557
TITLE %TIME% _*.* IN WINFOLD LINE2 2597
TITLE %TIME% %%ZDATE1%% YQJ25 LINE2 3672
TITLE %TIME% FINISHED. PRESS ANY KEY TO SHUTDOWN ... LINE2 4922
'#
# Determine the full path to a sample file.
# NOTE: Using the *full* path is a *must* when calling .NET methods, because
# the latter generally don't see the same working dir. as PowerShell.
$file = "$PWD/test.bat"
# Create the sample file with the sample content repeated N times.
$repeatCount = 1000 # -> 6,000 lines
[IO.File]::WriteAllText($file, $fileContent * $repeatCount)
# Warm up the file cache and count the lines.
$lineCount = [IO.File]::ReadAllLines($file).Count
# Define the commands to compare as an array of scriptblocks.
$commands =
{ # switch -Regex -File + [IO.File]::Read/WriteAllLines()
$i = 0
$updatedLines = & { switch -Regex -File $file {
'^(.*? LINE2 )\d+(.*)$' { $Matches[1] + ++$i + $Matches[2] }
default { ++$lc; $_ }
} }
[IO.File]::WriteAllLines($file, $updatedLines, [text.encoding]::ASCII)
},
{ # Get-Content + -replace + Set-Content
(Get-Content $file) | ForEach-Object -Begin { $i = 1 } -Process {
$_ -replace "LINE2 \d*", "LINE2 $i"
++$i
} | Set-Content -Encoding Ascii $file
}
# In PS Core v6.1+, also test -replace with a scriptblock operand.
if ($PSVersionTable.PSVersion.Major -ge 6 -and $PSVersionTable.PSVersion.Minor -ge 1) {
$commands +=
{ # -replace with scriptblock + [IO.File]::Read/WriteAllLines()
$i = 0
[IO.File]::WriteAllLines($file,
([IO.File]::ReadAllLines($file) -replace '(?<= LINE2 )\d+', { (++$i) }),
[text.encoding]::ASCII
)
}
} else {
Write-Warning "Skipping -replace-with-scriptblock command, because it isn't supported in this PS version."
}
# How many runs to average.
$runs = 100
Write-Verbose -vb "Averaging $runs runs with a $lineCount-line file of size $('{0:N2} MB' -f ((Get-Item $file).Length / 1mb))..."
Time-Command -Count $runs -ScriptBlock $commands
Here are sample results from my Windows 10 machine (the absolute timings aren't important, but hopefully the relative performance show in in the Factor column is somewhat representative); the PowerShell Core version used is v6.2.0-preview.4
# Windows 10, Windows PowerShell v5.1
WARNING: Skipping -replace-with-scriptblock command, because it isn't supported in this PS version.
VERBOSE: Averaging 100 runs with a 6000-line file of size 0.29 MB...
Factor Secs (100-run avg.) Command
------ ------------------- -------
1.00 0.108 # switch -Regex -File + [IO.File]::Read/WriteAllLines()...
4.22 0.455 # Get-Content + -replace + Set-Content...
# Windows 10, PowerShell Core v6.2.0-preview 4
VERBOSE: Averaging 100 runs with a 6000-line file of size 0.29 MB...
Factor Secs (100-run avg.) Command
------ ------------------- -------
1.00 0.101 # switch -Regex -File + [IO.File]::Read/WriteAllLines()…
1.67 0.169 # -replace with scriptblock + [IO.File]::Read/WriteAllLines()…
4.98 0.503 # Get-Content + -replace + Set-Content…
How do you create a unix file format in Powershell? I am using the following to create a file, but it always creates it in the windows format.
"hello world" | out-file -filepath test.txt -append
As I understand, the new line characters CRLF make it to be a Windows format file whereas the unix format needs only a LF at the end of the line. I tried replacing the CRLF with the following, but it didn't work
"hello world" | %{ $_.Replace("`r`n","`n") } | out-file -filepath test.txt -append
There is a Cmdlet in the PowerShell Community Extensions called ConvertTo-UnixLineEnding
One ugly-looking answer is (taking input from dos.txt outputting to unix.txt):
[string]::Join( "`n", (gc dos.txt)) | sc unix.txt
but I would really like to be able to make Set-Content do this by itself and this solution does not stream and therefore does not work well on large files...
And this solution will end the file with a DOS line ending as well... so it is not 100%
I've found that solution:
sc unix.txt ([byte[]][char[]] "$contenttext") -Encoding Byte
posted above, fails on encoding convertions in some cases.
So, here is yet another solution (a bit more verbose, but it works directly with bytes):
function ConvertTo-LinuxLineEndings($path) {
$oldBytes = [io.file]::ReadAllBytes($path)
if (!$oldBytes.Length) {
return;
}
[byte[]]$newBytes = #()
[byte[]]::Resize([ref]$newBytes, $oldBytes.Length)
$newLength = 0
for ($i = 0; $i -lt $oldBytes.Length - 1; $i++) {
if (($oldBytes[$i] -eq [byte][char]"`r") -and ($oldBytes[$i + 1] -eq [byte][char]"`n")) {
continue;
}
$newBytes[$newLength++] = $oldBytes[$i]
}
$newBytes[$newLength++] = $oldBytes[$oldBytes.Length - 1]
[byte[]]::Resize([ref]$newBytes, $newLength)
[io.file]::WriteAllBytes($path, $newBytes)
}
make your file in the Windows CRLF format. then convert all lines to Unix format in new file:
$streamWriter = New-Object System.IO.StreamWriter("\\wsl.localhost\Ubuntu\home\user1\.bashrc2")
$streamWriter.NewLine = "`n"
gc "\\wsl.localhost\Ubuntu\home\user1\.bashrc" | % {$streamWriter.WriteLine($_)}
$streamWriter.Flush()
$streamWriter.Close()
not a one-liner, but works for all lines, including EOF. new file now shows as Unix format in Notepad on Win11.
delete original file & rename new file to original, if you like:
ri "\\wsl.localhost\Ubuntu\home\user1\.bashrc" -Force
rni "\\wsl.localhost\Ubuntu\home\user1\.bashrc2" "\\wsl.localhost\Ubuntu\home\user1\.bashrc"
Two more examples on how you can replace CRLF by LF:
Example:
(Get-Content -Raw test.txt) -replace "`r`n","`n" | Set-Content test.txt -NoNewline
Example:
[IO.File]::WriteAllText('C:\test.txt', ([IO.File]::ReadAllText('C:\test.txt') -replace "`r`n","`n"))
Be aware, this does really just replace CRLF by LF. You might need to add a trailing LF if your Windows file does not contain a trailing CRLF.
Does anybody know how to determine the location of a file that's in one of the folders specified by the PATH environmental variable other than doing a dir filename.exe /s from the root folder?
I know this is stretching the bounds of a programming question but this is useful for deployment-related issues, also I need to examine the dependencies of an executable. :-)
You can use the where.exe utility in the C:\Windows\System32 directory.
For WindowsNT-based systems:
for %i in (file) do #echo %~dp$PATH:i
Replace file with the name of the file you're looking for.
If you want to locate the file at the API level, you can use PathFindOnPath. It has the added bonus of being able to specify additional directories, in case you want to search in additional locations apart from just the system or current user path.
On windows i'd say use %WINDIR%\system32\where.exe
Your questions title doesn't specify windows so I imagine some folks might find this question looking for the same with a posix OS on their mind (like myself).
This php snippet might help them:
<?php
function Find( $file )
{
foreach( explode( ':', $_ENV( 'PATH' ) ) as $dir )
{
$command = sprintf( 'find -L %s -name "%s" -print', $dir, $file );
$output = array();
$result = -1;
exec( $command, $output, $result );
if ( count( $output ) == 1 )
{
return( $output[ 0 ] );
}
}
return null;
}
?>
This is slightly altered production code I'm running on several servers. (i.e. taken out of OO context and left some sanitation and error checking out for brevity.)
Using PowerShell on Windows...
Function Get-ENVPathFolders {
#.Synopsis Split $env:Path into an array
#.Notes
# - Handle 1) folders ending in a backslash 2) double-quoted folders 3) folders with semicolons 4) folders with spaces 5) double-semicolons i.e. blanks
# - Example path: 'C:\WINDOWS\;"C:\Path with semicolon; in the middle";"E:\Path with semicolon at the end;";;C:\Program Files;
# - 2018/01/30 by Chad#ChadsTech.net - Created
$NewPath = #()
$env:Path.ToString().TrimEnd(';') -split '(?=["])' | ForEach-Object { #remove a trailing semicolon from the path then split it into an array using a double-quote as the delimeter keeping the delimeter
If ($_ -eq '";') { # throw away a blank line
} ElseIf ($_.ToString().StartsWith('";')) { # if line starts with "; remove the "; and any trailing backslash
$NewPath += ($_.ToString().TrimStart('";')).TrimEnd('\')
} ElseIf ($_.ToString().StartsWith('"')) { # if line starts with " remove the " and any trailing backslash
$NewPath += ($_.ToString().TrimStart('"')).TrimEnd('\') #$_ + '"'
} Else { # split by semicolon and remove any trailing backslash
$_.ToString().Split(';') | ForEach-Object { If ($_.Length -gt 0) { $NewPath += $_.TrimEnd('\') } }
}
}
Return $NewPath
}
$myFile = 'desktop.ini'
Get-ENVPathFolders | ForEach-Object { If (Test-Path -Path $_\$myFile) { Write-Output "Found [$_\$myFile]" } }
I also blogged the answer with some details over at http://blogs.catapultsystems.com/chsimmons/archive/2018/01/30/parse-envpath-with-powershell
In addition to the 'which' (MS Windows) and 'where' (unix/linux) utilities, I have written my own utility which I call 'findinpath'. In addition to finding the executable that would be executed, if handed to the command line interpreter (CLI), it will find all matches, returned path-search-order so you can find path-order problems. In addition, my utility returns not just executables, but any file-specification match, to catch those times when a desired file isn't actually executable.
I also added a feature that has turned out to be very nifty; the -s flag tells it to search not just the system path, but everything on the system disk, known user-directories excluded. I have found this feature to be incredibly useful in systems administration tasks...
Here's the 'usage' output:
usage: findinpath [ -p <path> | -path <path> ] | [ -s | -system ] <file>
or findinpath [ -h | -help ]
where: <file> may be any file spec, including wild cards
-h or -help returns this text
-p or -path uses the specified path instead of the PATH environment variable.
-s or -system searches the system disk, skipping /d /l/ /nfs and /users
Writing such a utility is not hard and I'll leave it as an exercise for the reader. Or, if asked here, I'll post my script - its in 'bash'.
just for kicks, here's a one-liner powershell implementation
function PSwhere($file) { $env:Path.Split(";") | ? { test-path $_\$file* } }