Linking two array variables in one line - powershell

Right now, I have an array called $vlog that shows the most recent instance of each log file in a certain folder $logpathfor each version of a particular version of a service $verno running on a machine. It displays the filename and the last write time.
$VLog = foreach ($log in $verno) {
Get-ChildItem $LogPath |
Where-Object {$_.Name -like "*$log-*"} |
Select-Object -last 1 | Select-Object Name, LastWriteTime
}
Furthermore, I'm using a mix of Trim() and -replace to cut most of the name down to ONLY show the corresponding version number contained within the log file name.
$vname = $vlog.name.Trim('service.logfile.prefix-') -replace ".{9}$"
Basically, it's cutting the end of the file (containing a date and the extension .log), as well as a specific preceding bit of text that's a constant at the beginning of each file. This all works great.
Now here's the trouble.
I need to get two items from this logfile data ($vname, which is the version number), and the corresponding LastWriteTime for the logfile for THAT specific version. I need that as an array, which will be further put into a variable for an email
What I need it to say is Service instance version #VERSION# was last logged at #VERSION'S LAST WRITE TIME#.
This is what I have right now:
$mailvar = foreach ($version in $vname) {
"Service instance version $version was last logged at $PLEASEHELP."
}
I can get it to display the correct number of instances of that sentence, with the correct version number per sentence, but I can't get the time to display the last write time corresponding to that version number.

I believe what you're looking for is the FileInfo property LastWriteTime. Here's your example simplified:
foreach ($log in $verno) {
$file = (Get-ChildItem -Path $LogPath -Filter "*$log-*")[-1]
$version = $file.Name.Trim('service.logfile.prefix-') -replace '.{9}$'
$stamp = $file.LastWriteTime | Get-Date -UFormat '%Y-%m-%d %H:%M:%S'
'Service instance version {0} was last logged at {1}' -f $version, $stamp
}

Related

Using Variables with Directories & Filtering

I'm new to PowerShell, and trying to do something pretty simple (I think). I'm trying to filter down the results of a folder, where I only look at files that start with e02. I tried creating a variable for my folder path, and a variable for the filtered down version. When I get-ChildItem for that filtered down version, it brings back all results. I'm trying to run a loop where I'd rename these files.
File names will be something like e021234, e021235, e021236, I get new files every month with a weird extension I convert to txt. They're always the same couple names, and each file has its own name I'd rename it to. Like e021234 might be Program Alpha.
set-location "C:\MYPATH\SAMPLE\"
$dir = "C:\MYPATH\SAMPLE\"
$dirFiltered= get-childItem $dir | where-Object { $_.baseName -like "e02*" }
get-childItem $dirFiltered |
Foreach-Object {
$name = if ($_.BaseName -eq "e024") {"Four"}
elseif ($_.BaseName -eq "e023") {"Three"}
get-childitem $dirFiltered | rename-item -newname { $name + ".txt"}
}
There are a few things I can see that could use some adjustment.
My first thought on this is to reduce the number of places a script has to be edited when changes are needed. I suggest assigning the working directory variable first.
Next, reduce the number of times information is pulled. The Get-ChildItem cmdlet offers an integrated -Filter parameter which is usually more efficient than gathering all the results and filtering afterward. Since we can grab the filtered list right off the bat, the results can be piped directly to the ForEach block without going through the variable assignment and secondary filtering.
Then, make sure to initialize $name inside the loop so it doesn't accidentally cause issues. This is because $name remains set to the last value it matched in the if/elseif statements after the script runs.
Next, make use of the fact that $name is null so that files that don't match your criteria won't be renamed to ".txt".
Finally, perform the rename operation using the $_ automatic variable representing the current object instead of pulling the information with Get-ChildItem again. The curly braces have also been replaced with parenthesis because of the change in the Rename-Item syntax.
Updated script:
$dir = "C:\MYPATH\SAMPLE\"
Set-Location $dir
Get-ChildItem $dir -Filter "e02*" |
Foreach-Object {
$name = $null #initialize name to prevent interference from previous runs
$name = if ($_.BaseName -eq "e024") {"Four"}
elseif ($_.BaseName -eq "e023") {"Three"}
if ($name -ne $null) {
Rename-Item $_ -NewName ($name + ".txt")
}
}

Cataloging file names over 256 characters

I am brand new to scripting (or coding of any sort). I had an issue where I wanted to generate csv files to catalog directories and certain file names to aid in my work. I was able to put something together that works for what I need. With one exception, long names return the following error:
ERROR: The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.
Here is my script:
Write-Host "Andy's File Lister v2.2"
$drive = Read-Host "R or Q?"
$client = Read-Host "What is the client's name as it appears on the R or Q drive?"
$path= "${drive}:\${client}"
Get-ChildItem $path -Recurse -dir | Select-Object FullName | Export-CSV $home\downloads\"$client directories.csv"
Get-ChildItem $path -Recurse -Include *.pdf, *.jp*, *.xl*, *.doc* | Select-Object FullName | Export-CSV $home\downloads\"$client files.csv"
Write-Host "Check your downloads folder."
Pause
As I said, I am brand new to this. Is there a different command I could use, or a way to tell the script to skip directory names or files over a certain length?
Thanks!
You can check the value of the .Length child property of the .FullName property of each item you check, and if it's greater than 256 characters, use Out-Null:
Ex.
$items = Get-ChildItem -Path C:\users\myusername\desktop\myfolder
foreach($item in $items)
{
if($item.FullName.Length -lt 256)
{
do some stuff
}
elseif($item.FullName.Length)
{
Out-Null
}
}
If you want to check the parent folder's path as well, you could check
$item.Parent.FullName.Length
in your processing as well.
I think you should close your strings on lines 5 and 6.
Instead of using ", you should use \" because currently your script parses the entire line 6 as a one string.

Finding entries in a log file greater than a defined time

My company needs to analyse log files of a Tomcat to check for specific errors and uses Powershell. Those errors will be stored in an array and checked against 1:1. This happens every 30 minutes by using Windows Task Scheduler. In case such an error is found in the log file, a generated text file will be sent to the administrators.
However it is only of interest to check for errors during the last 30 minutes, not beforehand.
So I have defined first a variable for the time:
$logTimeStart = (Get-Date).AddMinutes(-30).ToString("yyyy-MM-dd HH:mm:ss")
Later on I check for the existence of such an error:
$request = Get-Content ($logFile) | select -last 100 | where { $_ -match $errorName -and $_ -gt $logTimeStart }
Unfortunately this does not work; it also sends errors happened before this interval of 30 minutes.
Here is an extract of the Tomcat log:
2016-05-25 14:21:30,669 FATAL [http-apr-8080-exec-4] admins#company.de de.abc.def.business.service.ExplorerService GH00000476:
de.abc.def.business.VisjBusinessException: Invalid InstanceId
at de.abc.def.business.service.ExplorerService$ExplorerServiceStatic.getExplorer(ExplorerService.java:721)
at de.abc.def.business.service.ExplorerService$ExplorerServiceStatic.getTreeItemList(ExplorerService.java:823)
at sun.reflect.GeneratedMethodAccessor141.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at de.abc.def.business.provider.ServiceProvider.callServiceMethod(ServiceProvider.java:258)
at de.abc.def.business.communication.web.client.ServiceDirectWrapperDelegate.callServiceMethod(ServiceDirectWrapperDelegate.java:119)
at de.abc.def.business.communication.web.client.ServiceWrapperBase.callServiceMethod(ServiceWrapperBase.java:196)
at de.abc.def.business.communication.web.client.ServiceDirectWrapper.callServiceMethod(ServiceDirectWrapper.java:24)
at de.abc.def.web.app.service.stub.AbstractBaseStub.callServiceMethodDirect(AbstractBaseStub.java:72)
at de.abc.def.web.app.service.stub.AbstractBaseStub.callServiceMethod(AbstractBaseStub.java:183)
at de.abc.def.web.app.service.stub.StubSrvExplorer.getTreeItemList(StubSrvExplorer.java:135)
at de.abc.def.web.app.resource.servlet.ExplorerServlet.createXml(ExplorerServlet.java:350)
at de.abc.def.web.app.resource.servlet.ExplorerServlet.callExplorerServlet(ExplorerServlet.java:101)
at de.abc.def.web.app.resource.servlet.VisServlet.handleServlets(VisServlet.java:244)
at de.abc.def.web.app.FlowControlAction.isPing(FlowControlAction.java:148)
at de.abc.def.web.app.FlowControlAction.execute(FlowControlAction.java:101)
at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:484)
at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:274)
Unfortunately one cannot say how many lines of such an error will show up. Therefore 100 is just an estimate (which works well).
So how to change the related line
$request = Get-Content ($logFile) | select -last 100 |
where { $_ -match $errorName -and $_ -gt $logTimeStart }
to a correct one?
Use Select-String and a regular expression to parse your log file. Basically a log entry consists of a timestamp, the severity, and a message (which may span several lines). A regular expression for that might look like this:
(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3})\s+(\w+)\s+(.*(?:\n\D.*)*(?:\n\t.*)*)
(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}) matches the timestamp.
(\w+) matches the severity.
(.*(?:\n\D.*)*) matches the log message (the current line followed by zero or more lines not beginning with a number).
The parentheses around each subexpression capture the submatch as a group that can then be used for populating the properties of custom objects.
$datefmt = 'yyyy-MM-dd HH:mm:ss,FFF'
$culture = [Globalization.CultureInfo]::InvariantCulture
$pattern = '(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3})\s+(\w+)\s+(.*(?:\r\n\D.*)*)'
$file = 'C:\path\to\your.log'
Get-Content $file -Raw | Select-String $pattern -AllMatches | ForEach-Object {
$_.Matches | ForEach-Object {
New-Object -Type PSObject -Property #{
Timestamp = [DateTime]::ParseExact($_.Groups[1].Value, $datefmt, $culture)
Severity = $_.Groups[2].Value
Message = $_.Groups[3].Value
}
}
}
Parsing the date substring into a DateTime value isn't actually required (since date strings in ISO format can be sorted properly even with string comparisons), but it's nice to have so you don't have to convert your reference timestamp to a formatted string.
Note that you need to read the entire log file as a single string for this to work. In PowerShell v3 and newer this can be achieved by calling Get-Content with the parameter -Raw. On earlier versions you can pipe the output of Get-Content through the Out-String cmdlet to get the same result:
Get-Content $file | Out-String | ...

Counting files that start with the same characters

I'm trying to get a count of all files in a directory whose first few characters match a string. Here is the function:
function CountMatchingID($d1,$s){
$d = Get-ChildItem -filter $s -path $d1 | Measure-Object
return $d.count
}
I'm passing in the directory and a variable with a wildcard. It is returning the total count of files in the directory. However if I change the $s inside the function to the value of that variable like below, it will return the accurate number of files that have that beginning.
function CountMatchingID($d1,$s){
$d = Get-ChildItem -filter "201*" -path $d1 | Measure-Object
return $d.count
}
The files are employee id photos with employee numbers at the beginning and I'm trying to automate searching a csv file to rename them to the username so we can dump them into a directory for Jabber to show the photos.
I'm open to suggestions about a better way to accomplish this as well. I'm fairly new to Powershell so any feedback is welcome.
Issue might be what you are passing as $s for which you don't show an example. This works as expected for me
function CountMatchingID($d1,$s){
Get-ChildItem -filter "$s*" -path $d1 | Measure-Object | Select -ExpandProperty Count
}
I added the asterisk into the filter automatically so that it can just be assumed.
A sample call would look like this
CountMatchingID "c:\temp" "test"

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
}