Powershell - Count lines in files excluding/including certain lines - powershell

I have the script below that will count ALL the lines of all my files in the directory and sub-directory.
Works fine and creates the output file fine.
The issue I'm having now, is that all the files have comments and executable lines in them and I need to separate the two.
I have to count all the lines that have an Asterisk in position 7. These are comments. A simple calculation of total lines minus comment lines will provide the last artifact I need which is executable lines.
Can someone help out with altering the below code to just count Asterisk in position 7.
Thank you in advance,
-Ron
$path='C:\'
$outputFile='C:\Output.csv'
$include='*.cbl'
$exclude=''
param([string]$path, [string]$outputFile, [string]$include, [string]$exclude)
Clear-Host
Get-ChildItem -re -in $include -ex $exclude $path |
Foreach-Object { Write-Host "Counting '$($_.Name)'"
$fileStats = Get-Content $_.FullName | Measure-Object -line
$linesInFile = $fileStats.Lines
"$_,$linesInFile" } | Out-File $outputFile -encoding "UTF8"
Write-Host "Complete"

I would do something like this
$linesInFile = 0
switch -Regex -File $_.FullName {
'^.{6}\*' { <# don't count this line #> }
default { $linesInFile++ }
}
This also should be faster than using Get-Content.
P.S. Also adding -File to the Get-ChildItem helps to eliminate processing directories.

Related

Script lists all files that don't contain needed content

I'm trying to find all files in a dir, modified within the last 4 hours, that contain a string. I can't have the output show files that don't contain needed content. How do I change this so it only lists the filename and content found that matches the string, but not files that don't have that string? This is run as a windows shell command. The dir has a growing list of hundreds of files, and currently output looks like this:
File1.txt
File2.txt
File3.txt
... long long list, with none containing the needed string
(powershell "Set-Location -Path "E:\SDKLogs\Logs"; Get-Item *.* | Foreach { $lastupdatetime=$_.LastWriteTime; $nowtime = get-date; if (($nowtime - $lastupdatetime).totalhours -le 4) {Select-String -Path $_.Name -Pattern "'Found = 60.'"| Write-Host "$_.Name Found = 60"; }}")
I tried changing the location of the Write-Host but it's still printing all files.
Update:
I'm currently working on this fix. Hopefully it's what people were alluding to in comments.
$updateTimeRange=(get-date).addhours(-4)
$fileNames = Get-ChildItem -Path "K:\NotFound" -Recurse -Include *.*
foreach ($file in $filenames)
{
#$content = Get-Content $_.FullName
Write-host "$($file.LastWriteTime)"
if($file.LastWriteTime -ge $($updateTimeRange))
{
#Write-Host $file.FullName
if(Select-String -Path $file.FullName -Pattern 'Thread = 60')
{
Write-Host $file.FullName
}
}
}
If I understood you correctly, you just want to display the file name and the matched content? If so, the following will work for you:
$date = (Get-Date).AddHours(-4)
Get-ChildItem -Path 'E:\SDKLogs\Logs' | Where-Object -FilterScript { $date -lt $_.LastWriteTime } |
Select-String -Pattern 'Found = 60.' |
ForEach-Object -Process {
'{0} {1}' -f $_.FileName, $_.Matches.Value
}
Get-Date doesn't need to be in a variable before your call but, it can become computationally expensive running a call to it again and again. Rather, just place it in a variable before your expression and call on the already created value of $date.
Typically, and for best practice, you always want to filter as far left as possible in your command. In this case we swap your if statement for a Where-Object to filter as the objects are passed down the pipeline. Luckily for us, Select-String returns the file name of a match found, and the matched content so we just reference it in our Foreach-Object loop; could also use a calculated property instead.
As for your quoting issues, you may have to double quote or escape the quotes within the PowerShell.exe call for it to run properly.
Edit: swapped the double quotes for single quotes so you can wrap the entire expression in just PowerShell.exe -Command "expression here" without the need of escaping; this works if you're pattern to find doesn't contain single quotes.

How to select [n] Items from a CSV list to assign them to a variable and afterwards remove those items and save the file using PowerShell

I'm parsing a CSV file to get the names of folders which I need to copy to another location. Because there are hundreds of them, I need to select the first 10 or so and run the copy routine but to avoid copying them again I'm removing them from the list and saving the file.
I'll run this on a daily scheduled task to avoid having to wait for the folders to finish copying. I'm having a problem using the 'Select' and 'Skip' options in the code (see below), if I remove those lines the folders are copied (I'm using empty folders to test) but if I have them in, then nothing happens when I run this in PowerShell.
I looked around in other questions about similar issues but did not find anything that answers this particular issue selecting and skipping rows in the CSV.
$source_location = 'C:\Folders to Copy'
$folders_Needed = gci $source_location
Set-Location -Path $source_location
$Dest = 'C:\Transferred Folders'
$csv_name = 'C:\List of Folders.csv'
$csv_Import = Get-Content $csv_name
foreach($csv_n in $csv_Import | Select-Object -First 3){
foreach ($folder_Tocopy in $folders_Needed){
if("$folder_Tocopy" -contains "$csv_n"){
Copy-Item -Path $folder_Tocopy -Destination $Dest -Recurse -Verbose
}
}
$csv_Import | Select-Object -Skip 3 | Out-File -FilePath $csv_name
}
It should work with skip/first as in your example, but I cannot really test it without your sample data. Also, it seems wrong that you write the same output to the csv file at every iteration of the loop. And I assume it's not a csv file but actually just a plain text file, a list of folders? Just folder names or full paths? (I assume the first.)
Anyways, here is my suggested update to the script (see comments):
$source_location = 'C:\Folders to Copy'
$folders_Needed = Get-ChildItem $source_location
$Dest = 'C:\Transferred Folders'
$csv_name = 'C:\List of Folders.csv'
$csv_Import = #(Get-Content $csv_name)
# optional limit
# set this to $csv_Import.Count if you want to copy all folders
$limit = 10
# loop over the csv entries
for ($i = 0; $i -lt $csv_Import.Count -and $i -lt $limit; $i++) {
# current line in the csv file
$csv_n = $csv_Import[$i]
# copy the folder(s) which name matches the csv entry
$folders_Needed | where {$_.Name -eq $csv_n} | Copy-Item -Destination $Dest -Recurse -Verbose
# update the csv file (skip all processed entries)
$csv_Import | Select-Object -Skip ($i + 1) | Out-File -FilePath $csv_name
}

Powershell: Logging foreach changes

I have put together a script inspired from a number of sources. The purpose of the powershell script is to scan a directory for files (.SQL), copy all of it to a new directory (retain the original), and scan each file against a list file (CSV format - containing 2 columns: OldValue,NewValue), and replace any strings that matches. What works: moving, modifying, log creation.
What doesn't work:
Recording in the .log for the changes made by the script.
Sample usage: .\ConvertSQL.ps1 -List .\EVar.csv -Files \SQLFiles\Rel_1
Param (
[String]$List = "*.csv",
[String]$Files = "*.sql"
)
function Get-TimeStamp {
return "[{0:dd/MM/yyyy} {0:HH:mm:ss}]" -f (Get-Date)
}
$CustomFiles = "$Files\CUSTOMISED"
IF (-Not (Test-Path $CustomFiles))
{
MD -Path $CustomFiles
}
Copy-Item "$Files\*.sql" -Recurse -Destination "$CustomFiles"
$ReplacementList = Import-Csv $List;
Get-ChildItem $CustomFiles |
ForEach-Object {
$LogFile = "$CustomFiles\$_.$(Get-Date -Format dd_MM_yyyy).log"
Write-Output "$_ has been modified on $(Get-TimeStamp)." | Out-File "$LogFile"
$Content = Get-Content -Path $_.FullName;
foreach ($ReplacementItem in $ReplacementList)
{
$Content = $Content.Replace($ReplacementItem.OldValue, $ReplacementItem.NewValue)
}
Set-Content -Path $_.FullName -Value $Content
}
Thank you very much.
Edit: I've cleaned up a bit and removed my test logging files.
Here's the snippet of code that I've been testing with little success. I put the following right under $Content= Content.Replace($ReplacementItem.OldValue, $ReplacementItem.NewValue)
if ( $_.FullName -like '*TEST*' ) {
"This is a test." | Add-Content $LogFile
}
I've also tried to pipe out the Set-Content using Out-File. The outputs I end up with are either a full copy of the contents of my CSV file or the SQL file itself. I'll continue reading up on different methods. I simply want to, out of hundreds to a thousand or so lines, to be able to identify what variables in the SQL has been changed.
Instead of piping output to Add-Content, pipe the log output to: Out-File -Append
Edit: compare the content using the Compare-Object cmdlet and evaluate it's ouput to identify where the content in each string object differs.

Renaming a new folder file to the next incremental number with powershell script

I would really appreciate your help with this
I should first mention that I have been unable to find any specific solutions and I am very new to programming with powershell, hence my request
I wish to write (and later schedule) a script in powershell that looks for a file with a specific name - RFUNNEL and then renames this to R0000001. There will only be one of such 'RFUNELL' files in the folder at any time. However when next the script is run and finds a new RFUNNEL file I will this to be renamed to R0000002 and so on and so forth
I have struggled with this for some weeks now and the seemingly similar solutions that I have come across have not been of much help - perhaps because of my admittedly limited experience with powershell.
Others might be able to do this with less syntax, but try this:
$rootpath = "C:\derp"
if (Test-Path "$rootpath\RFUNNEL.txt")
{ $maxfile = Get-ChildItem $rootpath | ?{$_.BaseName -like "R[0-9][0-9][0-9][0-9][0-9][0-9][0-9]"} | Sort BaseName -Descending | Select -First 1 -Expand BaseName;
if (!$maxfile) { $maxfile = "R0000000" }
[int32]$filenumberint = $maxfile.substring(1); $filenumberint++
[string]$filenumberstring = ($filenumberint).ToString("0000000");
[string]$newName = ("R" + $filenumberstring + ".txt");
Rename-Item "$rootpath\RFUNNEL.txt" $newName;
}
Here's an alternative using regex:
[cmdletbinding()]
param()
$triggerFile = "RFUNNEL.txt"
$searchPattern = "R*.txt"
$nextAvailable = 0
# If the trigger file exists
if (Test-Path -Path $triggerFile)
{
# Get a list of files matching search pattern
$files = Get-ChildItem "$searchPattern" -exclude "$triggerFile"
if ($files)
{
# store the filenames in a simple array
$files = $files | select -expandProperty Name
$files | Write-Verbose
# Get next available file by carrying out a
# regex replace to extract the numeric part of the file and get the maximum number
$nextAvailable = ($files -replace '([a-z])(.*).txt', '$2' | measure-object -max).Maximum
}
# Add one to either the max or zero
$nextAvailable++
# Format the resulting string with leading zeros
$nextAvailableFileName = 'R{0:000000#}.txt' -f $nextAvailable
Write-Verbose "Next Available File: $nextAvailableFileName"
# rename the file
Rename-Item -Path $triggerFile -NewName $nextAvailableFileName
}

How to modify my powershell script to search for all jpg/jpeg on a machine

I am working on building a PowerShell script that will find all the jpeg/jpg files on a machine. This is what I have so far-
# PowerShell script to list the DLL files under the C drive
$Dir = get-childitem C:\ -recurse
# $Dir |get-member
$List = $Dir | where {$_.extension -eq ".jpg"}
$List |ft fullname |out-file C:\Users\User1\Desktop\dll.txt
# List | format-table name
The only problem is that some of the files I am looking for don't have the extension jpg/jpeg. I know that you can look in the header of the file and if it says ÿØÿà then it is a jpeg/jpg but I don't know how to incorporate this into the script.
Any help would be appreciated. Thanks so much!
I'm not sure how to use powershell native commands to Look at file headers, I will do some research on it because it sounds fun. Until then I can suggest a shorter version of your initial command, reducing it to a one liner.
Get-ChildItem -Recurse -include *.jpg | Format-table -Property Fullname | Out-file C:\Users\User1\Desktop\Jpg.txt
or
ls -r -inc *.jpg | ft Fullname
EDITED: removed redundant code, thanks #nick.
I'll let you know what I find if I find anything at all.
Chris
The following will retrieve files with either a .jpg/.jpeg extension or that contain a JPEG header in the first four bytes:
[Byte[]] $jpegHeader = 255, 216, 255, 224;
function IsJpegFile([System.IO.FileSystemInfo] $file)
{
# Exclude directories
if ($file -isnot [System.IO.FileInfo])
{
return $false;
}
# Include files with either a .jpg or .jpeg extension, case insensitive
if ($file.Extension -match '^\.jpe?g$')
{
return $true;
}
# Read up to the first $jpegHeader.Length bytes from $file
[Byte[]] $fileHeader = #(
Get-Content -Path $file.FullName -Encoding Byte -ReadCount 0 -TotalCount $jpegHeader.Length
);
if ($fileHeader.Length -ne $jpegHeader.Length)
{
# The length of the file is less than the JPEG header length
return $false;
}
# Compare each byte in the file header to the JPEG header
for ($i = 0; $i -lt $fileHeader.Length; $i++)
{
if ($fileHeader[$i] -ne $jpegHeader[$i])
{
return $false;
}
}
return $true;
}
[System.IO.FileInfo[]] $jpegFiles = #(
Get-ChildItem -Path 'C:\' -Recurse `
| Where-Object { IsJpegFile $_; }
);
$jpegFiles | Format-Table 'FullName' | Out-File 'C:\Users\User1\Desktop\dll.txt';
Note that the -Encoding and -TotalCount parameters of the Get-Content cmdlet are used to read only the first four bytes of each file, not the entire file. This is an important optimization as it avoids basically reading every byte of file data on your C: drive.
This should give you all files starting with the sequence "ÿØÿà":
$ref = [byte[]]#(255, 216, 255, 224)
Get-ChildItem C:\ -Recurse | ? { -not $_.PSIsContainer } | % {
$header = [System.IO.File]::ReadAllBytes($_.FullName)[0..3]
if ( (compare $ref $header) -eq $null ) {
$_.FullName
}
} | Out-File "C:\Users\User1\Desktop\dll.txt"
To find if the header starts with ÿØÿà, use:
[System.String]$imgInfo = get-content $_ # where $_ is a .jpg file such as "pic.jpg"
if($imgInfo.StartsWith("ÿØÿà"))
{
#It's a jpeg, start processing...
}
Hope this helps
I would recommend querying the windows search index for the jpegs, rather than trying to sniff file contents. Searching the system index using filenames is insanely fast, the downside is that you must search indexed locations.
I wrote a windows search querying script using the windows sdk \samples\windowssearch\oledb, you would want to query using the imaging properties. However, I'm not certain off the top of my head if the search index uses the imaging api to look at unknown files or files without extensions. Explorer seems to know my jpeg thumbnails and metadata without jpg extensions, so I'm guessing the indexer is going to be as clever as explorer.
How about this one?
jp*g is used to match both jpg and jepg images.
$List = Get-ChildItem "C:\*.jp*g" -Recurse
$List |ft fullname |out-file C:\Users\User1\Desktop\dll.txt