This code below allows me to find the word "error" in all my files using command-line (CMD).
find /c "error" C:\MyFiles\*.txt
But I want it to look for the word "error" and "warning" at the same time.
So I want to put these 2 lines in as 1 line of command.
find /c "error" C:\MyFiles\*.txt
find /c "warning" C:\MyFiles\*.txt
I do not want to see 2 result sets.
This should work (FINDSTR splits the search string at spaces, so it looks for either word).
findstr "error warning" C:\MyFiles\*.txt
As should this equivalent search:
findstr /c:"error" /c:"warning" C:\MyFiles\*.txt
However, there is this bug: Why doesn't this FINDSTR example with multiple literal search strings find a match?. I'm not sure if the above searches would meet the criteria that specify when the bug might affect the results. (I'm not sure if there enough overlap between the search strings.) But better to be safe than sorry.
You can eliminate the possibility of the bug by either making the search case insensitive (not sure if that meets your requirements or not):
findstr /i "error warning" C:\MyFiles\*.txt
or you can convert your search strings into regular expressions. This is trivial to implement in your case since there are no regex meta-characters that need escaping in your search strings.
findstr /r "error warning" C:\MyFiles\*.txt
In your case you could simple use a pipe for realize the AND operator.
find /c "error" C:\MyFiles\*.txt | find /c "warning"
From your comment, you need an OR operator
In this case I would do the search with findstr and the counting with find /c
findstr "error warning" C:\MyFiles\*.txt | find /c /v ""
Related
I did a ton of reading and searching about a way to have Get-ChildItem return a dir listing in wide format, in alphabetical order, with the number of files and directories in the current directory. Here is a image of what I ended up with, but not using GCI.
I ended up writing a small PS file.
$bArgs = "--%/c"
$cArgs = "Dir /n/w"
& cmd.exe -ArgumentList $bArgs $cArgs
As you can see I ended up using the old cmd.exe and passing the variables I wanted. I made an alias in my PS $Profile to call this script.
Can this not be accomplished in PS v5.1? Thanks for any help or advice for an old noob.
PowerShell's for-display formatting differs from cmd.exe's, so if you want the formatting of the latter's internal dir command, you'll indeed have to call it via cmd /c, via a function you can place in your $PROFILE file (note that aliases in PowerShell are merely alternative names and can therefore not include baked-in arguments):
function lss { cmd /c dir /n /w /c $args }
Note that you lose a key benefit of PowerShell: the ability to process rich objects:
PowerShell-native commands output rich objects that enable robust programmatic processing; e.g., Get-ChildItem outputs System.IO.FileInfo and System.IO.DirectoryInfo instances; the aspect of for-display formatting is decoupled from the data output, and for-display formatting only kicks in when printing to the display (host), or when explicitly requested.
For instance, (Get-ChildItem -File).Name returns an array of all file names in the current directory.
By contrast, PowerShell can only use text to communicate with external programs, which makes processing cumbersome and brittle, if information must be extracted via text parsing.
As Pierre-Alain Vigeant notes, the following PowerShell command gives you at least similar output formatting as your dir command, though it lacks the combined-size and bytes-free summary at the bottom:
Get-ChildItem | Format-Wide -AutoSize
To wrap that up in a function, use:
function lss { Get-ChildItem #args | Format-Wide -Autosize }
Note, however, that - due to use of a Format-* cmdlet, all of which output objects that are formatting instructions rather than data - this function's output is also not suited to further programmatic processing.
A proper solution would require you to author custom formatting data and associate them with the System.IO.FileInfo and System.IO.DirectoryInfo types, which is nontrivial however.
See the conceptual about_Format.ps1xml help topic, Export-FormatData, Update-FormatData, and this answer for a simple example.
How do I display multiple characters in a MS-Dos command
So for example, if I want to display everything in a current directory that begins with A, I might type the following command into CMD.
dir/b A*
But what if I want to display everything A-M? How would I go about telling the computer to display multiple characters?
(this is for PowerShell) ls command for listing the current directory and after you add A*.
so the command is ls A*(it will list name starting with a as well).
If you want to filter using regular expression
Get-ChildItem | Where-Object { $_.Name -Match 'RegularExpression' }
A regular expression for starts with a letter is ^[A-M]+.
Change -Match to CMatch for capital sensitive.
Using cmd leaves you two possibilities: list files for each desired letter:
dir /b a* b* c* d* ... m*
which is awkward and inflexible because of hardcoding the list. Even using a for loop isn't better:
for %a in (a b c d e ... m) do #dir /b %a*
This might work fine if you want just two or three letters.
The second (much more flexible and probably faster too) method: get the whole list and just filter it:
dir /b | findstr /rib "[a-m]"
For not-consecutive letters (a*, b*, m*, n*, x*, y*):
dir /b | findstr /rib "[abmnxy]"
I run subsequent reg queries and this takes quite a time:
reg query HKLM\SOFTWARE\Classes /s /f "foo"
reg query HKLM\SOFTWARE\Classes /s /f "bar"
Is there any way to search by multiple values at once with reg query?
No, unfortunately reg query /s /f accepts only a single filter expression.
The filter expression:Tip of the hat to aschipfl for his help.
is matched against all registry entities by default: key names, value names and data.
(OR-ed combinations of) options /k (keys), /v (values) and /d (data) can be used to narrow the scope.
/v can also be used without /f, in which case it requires a value-name search term (e.g., /v foo) that is matched in full (see below); /ve returns only default values (the values whose name is the empty string) if they contain data.
When combining /f with /v <valueNameSearchTerm> or /ve, only combining key search (/k) via /f is supported to narrow down the matches; that is, the only combinations that make sense are:
/f <keySearchTerm> /k /v <valueNameSearchTerm>
/f <keySearchTerm> /k /ve
That way, the /v <valueNameSearchTerm> / /ve matching is limited to those keys that match /f <keySearchTerm>, amounting to AND logic.
Any other combination - omitting /k, adding /d, using just /d - effectively causes the /f search term to be ignored.
/t REG_* can be used to narrow matching to specified value types, such as REG_SZ
performs case-insensitive substring matching by default.
supports wildcard characters * (any number of chars., including none) and ? (exactly 1 char.) - though note that something like foo* still only performs substring matching; there is seemingly no way to anchor substrings.
As stated, when you use /v directly with a value-name search term (e.g., /v foo, it must match in full; e.g., to find a value name that contains substring foo, you then must use *foo*.
/e performs whole-string matching, without wildcard support.
/c uses case-sensitive matching.
numeric data such as REG_DWORD is matched in its decimal string representation
binary data (REG_BINARY) is matched as a "byte string": a list of 2-hex-digit byte values without separators.
Run reg query /? to see all options or consult the documentation.
You can use the following PowerShell command to provide multiple filters:
Note:
The command is limited to the following search logic - though it could be adapted to support all reg query options, at which point creating a function wrapper would definitely be called for:
A regular expression (with -match) rather than wildcard matching (with -like) is used, which both simplifies the command and makes it more flexible (it wouldn't be hard to adapt the solution to use wildcard matching instead).
Only registry data is searched, not also key names and value names.
For instance, to search key names only, the command would be as simple as:
Get-ChildItem HKLM:\SOFTWARE\Classes -Recurse |
Where-Object { $_.PSChildName -match 'foo|bar' }
Unlike with reg.exe, binary data is matched byte by byte, based on their decimal string representation.
Only the sub-keys of the target key are examined, not the target key itself.
With a single filter, the command is slower than reg.exe, but with multiple filters it is eventually likely faster than multiple reg.exe calls; for the OP it took 4-5 - YMMV.
Replacing the Get-ChildItem call with direct use of the .NET framework for recursive key enumeration will likely bring speed improvements, though I have no sense of how much. A purpose-built native binary such as reg.exe will always be faster than custom PowerShell code.
Generally, the main advantages of a PowerShell solution are:
Objects are being returned, which greatly facilitates subsequent processing (no need for parsing text output).
The use of regular expressions allows for more sophisticated matching.
# The two filters to use, combined into a single regex.
$regex = 'foo|bar'
Get-ChildItem HKLM:\SOFTWARE\Classes -Recurse | ForEach-Object {
foreach ($value in $_.GetValueNames()) {
if (($data = $_.GetValue($value)) -match $regex) {
[PSCustomObject]#{
Key = $_.Name
Value = if ($value) { $value } else { '(default)' }
Data = $data
}
}
}
}
The output is something like the following, with the Data column containing the matches (scroll to the right; alternatively, pipe the above to Format-List for a one-property-per-line view):
Key Value Data
--- ----- ----
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\AllSyncRootObjects StatusBar prop:~System.StatusBarViewItemCount;~System.StatusBarSelectedItemCount...
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{0002E132-0000-0000-C000-000000000046}\InprocServer32 Class Microsoft.Vbe.Interop.CommandBarEventsClass
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{0002E132-0000-0000-C000-000000000046}\InprocServe... Class Microsoft.Vbe.Interop.CommandBarEventsClass
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{0006F054-0000-0000-C000-000000000046} (default) Microsoft Outlook InfoBar Control
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{0006F054-0000-0000-C000-000000000046}\InprocServer32 Class Microsoft.Office.Interop.Outlook.OlkInfoBarClass
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{0006F054-0000-0000-C000-000000000046}\InprocServe... Class Microsoft.Office.Interop.Outlook.OlkInfoBarClass
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{0006F054-0000-0000-C000-000000000046}\ProgID (default) Outlook.OlkInfoBar.1
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{0006F054-0000-0000-C000-000000000046}\VersionInde... (default) Outlook.OlkInfoBar
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{056440FD-8568-48e7-A632-72157243B55B} (default) Explorer Navigation Bar
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{05d7b0f4-2121-4eff-bf6b-ed3f69b894d7} (default) Taskbar Control Panel
...
I currently have this script I run as a scheduled task:
#echo on
START /WAIT c:\windows\system32\Robocopy.exe "D:\folder" "\\192.168.2.87\folder" /E /MT:20 /R:50 /W:10 /V /ETA /LOG:c:\robocopy.txt
I want to convert and run this as a PowerShell script because of two reasons:
Its more modern
The most important reason is that I want to store the log with date and time as C:\robocopylog3004201510214043.txt and I am literally finding no information on how to strip characters like ":" and "/" from a batch script and I know PowerShell can.
That last number is not random. It is the date and time.
Date for example is "30/04/2015" (striping it would leave 30042015)
Time for example is "10:21:40,43" (striping it would leave 10214043)
So it would leave a file name of robocopylog3004201510214043.txt
There is little difference between CMD and PowerShell when it comes to running external programs. I'd recommend using the call operator (&), though, even if it isn't mandatory in this particular case.
& c:\windows\system32\Robocopy.exe "D:\folder" "\\192.168.2.87\folder" /E /MT:20 /R:50 /W:10 /V /ETA /LOG:c:\robocopy$(Get-Date -format 'ddMMyyyyHHmmss').txt
robocopy runs synchronously anyway, so no "wait" instruction required.
The number format ddMMyyyyHHmmss produces a timestamp consisting of day, month, year, hour, minutes and seconds. I wouldn't recommend to include milliseconds as well, because you probably won't run robocopy several times within the same second. If you must include milliseconds, append ff to the format string (or f, fff, etc., depending on the number of digits you need). You may want to consider using an ISO date format (yyyyMMddHHmmss), though, because that simplifies listing the log files in creation order.
As for replacing characters in batch scripts, that can be done via string replacements:
C:\>echo %DATE:/=%
30042015
C:\>echo %TIME::=_%
10_01_22.39
My Powershell script needs to invoke an EXE with a very complicated set of arguments. I'm using Powershell 3.0, and must stick with that version. Alas, even the "magic" escaping operator (--%) isn't helping me. For example, using the Call operator, consider this:
& other.exe --% action /mode fast /path:"location with spaces" /fancyparam { /dothis /dothat:"arg with spaces" } /verbose
Now, if it were that simple, my script could easily work fine. But it isn't that simple. The arguments for "other.exe" can be different, depending on user selections earlier in my script. So instead, I need to build up those parameters ahead of time, perhaps like this:
$commandArgs = 'action /mode ' + $userMode + ' /path:"location with spaces" /fancyparam { /dothis /dothat:"' + $userArgs + " } /verbose'
Thus I would invoke this way:
& other.exe --% $commandArgs
...well, expect that --% means it just passes a raw string of $commandArgs instead. But without the --%, powershell auto-quotes the contents of $commandArgs, which really messes up the internal quotes (not to mention breaking the 'action' argument at the front that other.exe needs first). In other words, I've already tried embedding the --% inside my $commandArgs string, but the damage is already done by the time it would be parsed (and I don't think it even works that way).
NOTE that this example is only about 1/4 of my actual command I need to execute -- which includes many more user args, quotes and other funny characters that would drive me into escaping-hell in a hurry! I've also already been using the echoargs.exe tool, which is how I'm seeing the troubles I'm having. Oh, and I need all the spaces in my example, too (i.e. need spaces around the brace characters).
So after much searching for an answer, I turn to you for help. Thanks in advance.
Ok, probably weird to be answering my own question, but after spending another day on this problem yesterday, I might have realized the answer myself. At least, this is what I found that works. But I post here to get further feedback, in case I'm really doing something that isn't recommended ...using Invoke-Expression :-)
I had sorta realized early on, and some of you confirmed this in your responses, that the --% prevents all further expansions (including my $variables). And my problem is that I'm still needing to expand lots of things when trying to use the Call operator (&). My problem would be solved if my command line was all ready to go before using --%, so that's what I did.
I created a new string, composed of:
$fullCommand = '& "other.exe" --% ' + $commandArgs
(The EXE path actually has spaces in it, hence the quotes.) Then, with it all built up (including the --% where it needs to be), I invoke it as a new piece of script:
Invoke-Expression $fullCommand
So far, I'm having very good results. But I know in my search up to this point that Invoke-Expression sounded like this bad thing people shouldn't use. Thoughts, everyone?
The purpose of --% is to suppress argument processing, so there's no expansion of variables after that parameter on behalf of PowerShell. You can work around this by using environment variables, though:
$env:UserMode = 'foo'
$env:UserArgs = 'bar baz'
& other.exe --% action /mode %UserMode% /path:"location with spaces" /fancyparam { /dothis /dothat:"%userArgs%" } /verbose
I always recommend that people build their command line arguments into a variable, and then pass that variable into the -ArgumentList parameter of the Start-Process cmdlet.
$Program = '{0}\other.exe' -f $PSScriptRoot;
$UserMode = 'fast';
$Path = 'c:\location with\spaces';
$ArgWithSpaces = 'arg with spaces';
$ArgumentList = 'action /mode {0} /path:"{1}" /fancyparam { /dothis /dothat:"{2}" } /verbose' -f $UserMode, $Path, $ArgWithSpaces;
$Output = '{0}\{1}.log' -f $env:Temp, [System.Guid]::NewGuid().ToString();
Start-Process -Wait -FilePath $Program -ArgumentList $ArgumentList -RedirectStandardOutput $Output;