Slow for loop iteration in batch file - powershell

So I'm making a batch script and I need to run a powershell command inside a for but it's running really slow, I don't know how to make it more efficient, I'm very new in this stuff. Here's part of my code:
for /f "tokens=*" %%G in (myfile.txt) do (
powershell -command '%%G' -replace ',+', ' ' >> newfile.txt
)

Since you say you are new to this. It's really important you get some ramp up on the topic. Using either all the free stuff Microsoft offers (Microsoft Virtual Academy or MS Channel9 or TechNet Virtual Labs or labsondemand), or youtube or at minimum review the help files first. There are lots of free eBooks as well and step by step blogs.
As the others have said, why not just do this in PS directly, to read and process the file. Though you don't have to, you can call a .ps1 from a .bat/.cmd/.vbs, etc., but you don't have to use it to process what PS can do.
# Giving a target file to be modified
# Check what is in the file
# Note: I am using Get-Content here, but if you have a .csv, then use the *csv* cmdlets
Get-Content -Path 'd:\temp\myfile.txt'
# Results
LIC,+CLIENT
12345,+Client1
54321,+Client2
34251,+Client3
# Test new file path
Test-Path -Path 'd:\temp\newfile.txt'
# Results
False
# Decide what to replace, code the replace
# and see what the new file content will look like when replaced
Get-Content -Path 'd:\temp\myfile.txt' |
ForEach{$_ -replace '\,\+',' '}
# Results
LIC CLIENT
12345 Client1
54321 Client2
34251 Client3
# Modify to send to a new file.
Get-Content -Path 'd:\temp\myfile.txt' |
ForEach{
$_ -replace '\,\+',' ' |
Out-File -FilePath 'D:\Temp\newfile.txt' -Append
}
# Results
Test-Path -Path 'd:\temp\newfile.txt'
True
Get-Content -Path 'd:\temp\newfile.txt'
# Results, should be the same as screen output.
LIC CLIENT
12345 Client1
54321 Client2
34251 Client3

Since you mentioned in your comment that you were trying to do it all in a batch script here is the basic syntax for string replacement. You have to first assign the FOR variable to an environmental variable before you can do any string replacement. You would also need to enable delayed expansion because you are manipulating a variable inside a parentheses code block.
#echo off
setlocal enabledelayedexpansion
(for /f "delims=" %%G in (myfile.txt) do (
set "line=%%G"
echo !line:+= !
)
)>newfile.txt
You could also do this without delayed expansion by using CALL ECHO
#echo off
(for /f "delims=" %%G in (myfile.txt) do (
set "line=%%G"
CALL echo %%line:+= %%
)
)>newfile.txt

Related

Comments in a long-line PowerShell code in a Batch script

I have a large batch script to which I need to add some Powershell code for some regex capture which I am unable to do in batch. I was hoping to have this code integrated in my batch script using the method outlined in Link, but when adding comments I get a missing } error. I've simplified my code just to be able to replicate the issue.
This, without a comment, works:
#echo OFF
setlocal enabledelayedexpansion enableextensions
set "var=variable"
PowerShell ^
foreach ($file in Get-ChildItem -File -Include *.* -Recurse) ^
{ ^
Write-Host $file; ^
Write-Host $env:var; ^
}
%End PowerShell%
echo Test
pause > nul
This, with a comment, does not work:
#echo OFF
setlocal enabledelayedexpansion enableextensions
set "var=variable"
PowerShell ^
foreach ($file in Get-ChildItem -File -Include *.* -Recurse) ^
{ ^
#Comment ^
Write-Host $file; ^
Write-Host $env:var; ^
}
%End PowerShell%
echo Test
pause > nul
I have tried escaping the # in a few different ways, but no matter what I do, I get the error message
Missing closing '}' in statement block or type definition.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : MissingEndCurlyBrace
The only difference is the comment. Does anyone know how to get around this? (using this long-line method that is). If it's not at all possible I guess I will go for base64 encoding
What works for me if when I do the comment line in between <# and #> as if it were a comment block.
Then of course for cmd you need to escape the < and > characters with a ^:
^<# Comment #^> ^
P.S. Don't forget that using Get-ChildItem without a -Path or -LiteralPath, the cmdlet will use PowerShell's current working folder ($pwd), which is most probably not the same as the current working path cmd uses..
This an hybrid code Batch and Powershell exmaple is just to show you how to put a multiline comment block with powershell and how to execute Batch section and powershell section :
<# : Batch Script Section
#rem # The previous line does nothing in Batch, but begins a multiline comment block in PowerShell. This allows a single script to be executed by both interpreters.
#echo off
Title Wifi Passwords Recovery by Hackoo 2022 & Mode 70,3
setlocal
cd "%~dp0"
Color 0B & echo(
Echo( Please Wait a while ... Getting SSID and Wifi Keys ...
Powershell -executionpolicy bypass -Command "Invoke-Expression $([System.IO.File]::ReadAllText('%~f0'))"
EndLocal
goto:eof
#>
# Powershell Script Section begin here...
# here we execute our powershell commands...
$Var=netsh wlan show profiles|SLS "\:(.+)$"|%{$SSID=$_.Matches.Groups[1].Value.Trim(); $_}|%{(netsh wlan show profile name="$SSID" key=clear)}|SLS "Conte.*:(.+)$"|%{$pass=$_.Matches.Groups[1].Value.Trim(); $_}|%{[PSCustomObject]#{SSID=$SSID;PASSWORD=$pass}}
$var | Format-List | Out-File -FilePath ".\WifiKeys_List_Format.txt"
$var | ConvertTo-Json | Out-File -FilePath ".\WifiKeys_JSON_Format.txt"
$var | OGV -Title "Wifi Passwords Recovery by Hackoo 2022" -wait
ii ".\WifiKeys_JSON_Format.txt"
ii ".\WifiKeys_List_Format.txt"

Want to retrieve the Modified date for list of file names present inside a file in CMD prompt

I have a list of file names present inside a file called My_text.txt, may be more than 100. I want to retrieve the Date modified, basically the DIR command output for all those file names.
My_Text.txt contains
D:\Users\dsa99_p\Desktop\My_Program1.txt
D:\Users\dsa99_p\Desktop\My_Program2.txt
D:\Users\dsa99_p\Desktop\My_Program3.txt
D:\Users\dsa99_p\Desktop\My_Program4.txt
and so on..
I want to retrieve the Date modified for all these My_Program1, My_Program2, My_Program3, My_Program4 files. How to do it? Please help.
If it's possible over Powershell then let me know.
In PowerShell the file content can be loaded by Get-Content and file information can be obtained with Get-ChildItem. So this is how it can be done in PowerShell:
Get-Content My_text.txt | ForEach-Object { (Get-ChildItem $_).LastWriteTime }
(Get-ChildItem (Get-Content My_text.txt)).LastWriteTime
Both commands do the same thing. Shorter form of them:
gc My_text.txt |% { (ls $_).LastWriteTime }
(ls (gc My_text.txt)).LastWriteTime
If you want a batch file solution
FOR /F "usebackq delims=" %%G IN ("My_Text.txt") DO ECHO FileName:%%G Modified:%%~tG
Because it is possible that one or more of the files may not exist, I would probably structure my code a little differently. I would first check whether each line related to an existing file, and only then get its information.
The first example I'll provide is for PowerShell, whilst it may seem like more text, it will be far more configurable, especially with regards to modifying the layout and content of the results.
powershell command line:
(Get-Content -Path '.\My_Text.txt') | ForEach-Object { If (Test-Path -LiteralPath $_ -PathType Leaf) { Get-Item -LiteralPath $_ | Select-Object -Property LastWriteTime, Name } }
cmd command line:
For /F "UseBackQ Delims=" %G In (".\My_Text.txt") Do #If "%~aG" Lss "d" If "%~aG" GEq "-" Echo %~tG: %~nxG
Single line batch-file version:
#(For /F "UseBackQ Delims=" %%G In (".\My_Text.txt") Do #If "%%~aG" Lss "d" If "%%~aG" GEq "-" Echo %%~tG: %%~nxG)&Pause
In all examples above, I have assumed that My_Text.txt is in the current directory, if it isn't please change its currently relative location .\ as necessary without modifying its quoting.

Check if a file with a pattern is available in the directory using batch script

A file is dropped in a directory when another job finished processing it.
The requirement is to run a batch script to check whether the file is available for today. and if available, I need to execute certain batch script. (Example: File with Naming pattern ABC-D*.txt should be available with modification date=today)
What I have figured out till now is:
FOR /F "tokens=1,2,3* Delims=/ " %%I in ('DATE /T') DO set TODAY=%%I-%%J-%%K
xcopy C:\BatchJobs\Odd*.* /L /D:%TODAY%
Running this is giving the output:
C:\BatchJobs\OddEven.txt
File cannot be copied onto itself
0 File(s)
C:\BatchJobs\OddEven.txt, showing in console is what I need. But I need to store it in some file or in some variable to be able to use this path later in my batch script. Can somebody help me in storing this file path in a variable or a file? Or suggest some other ways to achieve this goal?
You could use forfiles which is capable of filtering files by their last modification date, but not regarding the time:
forfiles /P "D:\ROOT" /M "ABC-D*.txt" /D +0 /C "cmd /C echo #file"
Instead of echo you can state your batch script to execute.
This code will identify "ABC-D*.txt" files last written today. Place the code below into a file such as doit.ps1 (but choose a better name). I did an attrib command on the file, but you will want to do something else.
$srcdir = 'C:\src\t\empty'
Get-ChildItem -File -Path $srcdir -Filter 'ABC-D*.txt' |
Where-Object { $_.LastWriteTime -ge [datetime]::Today } |
ForEach-Object {
# Do something to the $_ file
& attrib $_
}
Then, you can run it from a cmd shell with:
powershell -NoProfile -File doit.ps1

How to interrogate multiple text files for certain key words and output results in text?

Here is my objective:
I have a networked folder (Windows 7) that contains text log files that are generated on daily basis (1 log text per day), as a result file names contain various dates.
I need a script to go in and scan each new log file for a list of certain keywords: "ABC", "DEF", "GHI". Then write out how many of these events were found in each log file and save this info in a text format or similar.
Issues I'm facing: how do i get around various file names, each file name contains that days date. How do i look through a list of keywords and count all of these words in each log file. How do i write out the results in a separate text file. And finally is there a way to automate all of this?
I would like to stay in PowerShell or use Windows batch files.
I am afraid your request is not clear and have several missing details. However, here it is a Batch file with what I guess could be the first attempt to get a solution:
EDIT: I modified the original code in order to also group the files per event; the first code just groups events per file.
#echo off
setlocal EnableDelayedExpansion
rem Push dir to the networked folder
pushd "\\Path\to\networked\folder"
rem Count all file/event combinations in a two-dimensional array
for %%a in (ABC DEF GHI) do (
for /F "delims=:" %%b in ('findstr /I /L "%%a" logFiles*.txt') do (
set /A "count[%%~Nb][%%a]+=1"
)
)
rem Group events for the same file
for /F "tokens=2,3* delims=[]=" %%a in ('set count[') do (
set "file[%%a]=!file[%%a]!, %%b (%%c)"
)
rem Show the final result 1: events per file
(
echo Events per file:
for /F "tokens=2* delims=[]=" %%a in ('set file[') do (
set "line=%%b"
echo %%a.txt = !line:~2!
)
) > result1.txt
rem Group and count files for the same event
for /F "tokens=2,3* delims=[]=" %%a in ('set count[') do (
set "event[%%b]=!event[%%b]!, %%a.txt (%%c)"
set /A "eventCount[%%b]+=%%c"
)
rem Show the final result 2: files per event
(
echo Files per event:
for /F "tokens=2* delims=[]=" %%a in ('set event[') do (
set "line=%%b"
echo "%%a" - !eventCount[%%a]! found in !line:~2!
)
) > result2.txt
In PowerShell we can use Get-Childitem to collect which files we want to search though and store those in a variable as such:
$Content = Get-Content (Get-Childitem -Path C:\Temp\*.txt)
Once those text files are stored into our Content variable we can simply use the -match regex operator to find a match inside that collection of files.
$Content -match "Bacon"
You could then out this information using Out-File:
$Content -match "Bacon" | Out-file C:\Temp\Bacon.txt
Of course with the Get-child item you can use the DateTime operator and narrow down your query for only recently changed text files as required.
If you want to get more technical we can use Select-String to get this output for multiple matches as such:
Get-ChildItem C:\Logs |
where { $_ | Select-String -Pattern 'Blahblah' } |
where { $_ | Select-String -Pattern '\d{4}' } |
...
You can also do it on one line:
gci C:\Logs| select-string -pattern '(Bacon.*Failed)|(Failed.*Steak)'
Outputting is up to your imagination really. Once they are just simple strings they can be put into an array or a custom PS object for nice output. it would just require you to save the command you were running into a variable then call it later for the export.
for Grouping, take a look at Group-Object cmdlet. it will give you the occurrence numbers.

Parsing and removing text with cmd or powershell

I've checked quite a few other questions here but was unable to find what I need. I am currently trying to automate the process of finding out what VMs are in what Hyper V servers. Here is my current script.
#echo off
set /P dc= "Which DC do you want? "
dsquery computer -name %DC%VUT* > dsquery.txt
type dsquery.txt | findstr /v IMAGINGOU | findstr /v OTHEROU > dsquery2.txt
powershell -command Get-Content dsquery2.txt | ForEach-Object { $_ -replace '"CN=', "" } | Set-Content dsquery3.txt
powershell -command Get-Content dsquery3.txt | ForEach-Object { $_ -replace ',OU=CAM,OU=Exchange,OU=Server,DC=RED001,DC=local"', "" } | Set-Content serverlist.txt
powershell -command .\Get-HyperVMod.ps1 -file output.csv
The problem I have hit is the servers that I can do dsquery from (Win2k3) can't do the ForEach-Object because they have an older version of Powershell and the Win2k8 servers that can do the ForEach-Object can't do a dsquery.
Here is a sample of the dsquery output that I am trying to parse and remove all but the server name.
"CN=SERVER001,OU=VUT,OU=Infastructure,OU=Server,DC=dcname,DC=local"
"CN=SERVER008,OU=ImagingWDS,DC=red002,DC=local"
Output should be
SERVER001
SERVER008
Any suggestions on either doing this with another method or just my small problem of removing the extra text from the file.
run this against your dsquery.txt, it will parse what you need.
#echo off
setlocal enabledelayedexpansion
::Echos your Computer Name only.
for /F "tokens=1,2 delims==," %%G IN (dsquery.txt) DO echo %%H
pause
endlocal
basically what it does is uses two different delimeters: "=" and "," which immediately surround your server names. and looks only for that second token. It works based on the strings you provided above.
The ForEach-Object cmdlet was introduced in PowerShell V1 so you'll have it there.
Jon's regex here shows you how you can pull the common name from a LDAP string.
This will apply the regex to each line in a text file and return the result.
Get-Content file.txt | % { $_ -replace '(CN=)(.*?),.*', '$2' }
Update
Here's how your batch script would translate into PowerShell:
$dc = Read-Host "Which DC do you want?"
& dsquery computer -name "${dc}VUT*" | % {
# Do your processing here...
$_ -replace '(CN=)(.*?),.*', '$2'
}