Remove pattern from array in Powershell - powershell

$testing = #("a.txt", "b.txt")
$testing | Select-String???
I would like the output to only contain a, b.
My real example is:
$PackageNames = Get-ChildItem ("$DestinationPath\$ProjectName" + "_2105\" + $ProjectName) -Filter *.dtsx | Select-Object name -ExpandProperty name;
I need to use the content in this variable but without the extensions.

Working with files is easier, if you use FileInfo class' BaseName property. Like so,
$PackageNames = Get-ChildItem ("$DestinationPath\$ProjectName" + "_2105\" + $ProjectName) -Filter *.dtsx
# Process the results
foreach($pkg in $PackageNames) {
$pkg.BaseName # Just prints the file name without extension
}

Try this:
$a|%{($_.tostring().split("."))[0]}
Iterate over the array, split by a dot and and get the first item.

Related

How to initialise a custom object list in Powershell 5.1?

What I want to achieve:
Create a list of files, show their name and version and sort alphabetically.
My attempt:
class File_Information
{
[ValidateNotNullOrEmpty()][string]$Name
[ValidateNotNullOrEmpty()][string]$FileVersion
}
$FileList = New-Object System.Collections.Generic.List[File_Information]
foreach ($file in Get-Item("*.dll")){
$C = [File_Information]#{
Name = $file.Name
FileVersion = $file.VersionInfo.FileVersion
}
$FileList = $FileList.Add($C)
}
foreach ($file in Get-Item("*.exe")){
$C = [File_Information]#{
Name = $file.Name
FileVersion = $file.VersionInfo.FileVersion
}
$FileList = $FileList.Add($C)
}
Write-Output $FileList | Sort-Object -Property "Name" | Format-Table
My Powershell version:
Prompt> Get-Host | Select-Object Version
Version
-------
5.1.19041.1320
My problems and/or questions (first is the major question, second and third are optional):
I don't know how to initialise a list of custom objects (I've been looking on the site, but this question only gives the possibility to initialise with an existing value, while I want to initialise to an empty list).
The error I receive is:
You cannot call a method on a null-valued expression.
At line:... char:...
+ $FileList = $FileList.Add($C)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Currently I ask for the same thing twice: I have tried Get-Item(*.dll,*.exe), Get-Item(*.dll;*.exe) and Get-Item(*.dll|*.exe) but none of them worked.
Is it even possible to search for different patterns?
Until now I am struggling just to get Write-Output $FileList working. Are the other two commands (Sort-Object -Property "Name", Format-Table) correct?
Why use a class and a List object for that at all?
You can just use Select-Object on the files of interest you gather using Get-ChildItem and output the needed properties there:
# capture the resulting objects in a variable $result
$result = Get-ChildItem -Path 'X:\path\to\the\files' -File -Include '*.dll','*.exe' -Recurse |
Select-Object Name, #{Name = 'FileVersion'; Expression = {$_.VersionInfo.FileVersion}}
# output on screen
$result | Sort-Object Name | Format-Table -AutoSize
To use two different name patterns on Get-ChildItem, you need to also add switch -Recurse, or append \* to the path.
If you do not want recursion, you will need to add a Where-Object clause like:
$result = Get-ChildItem -Path 'X:\path with spaces' | Where-Object {$_.Extension -match '\.(dll|exe)'} |
Select-Object Name, #{Name = 'FileVersion'; Expression = {$_.VersionInfo.FileVersion}}
Your code actually works fine, with two small exceptions.
Problem #1: overly restrictive property attributes can cause errors
First off, in your custom class, you should be very sure you want to append this attribute [ValidateNotNullOrEmpty()], as it will throw a non-terminating error when you try to new up an instance of a class if any one of the properties with this attribute are null.
For instance, on my PC, I have a dll or two with empty file.VersionInfo.FileVersion properties. When that happens, PowerShell will fail to create the object, and then also fail trying to add the object to the list because there is no object. Very confusing.
Fix : remove the attribute of ValidateNotNullOrEmpty unless you're 100% positive this info is present.
Next up, the real problem here is that in each of your for-each loops, you're accidentally breaking your FileList object which you correctly setup in your code as it is already.
Problem #2: Small syntax issue is causing your list to be nulled out, causing errors for every op after the first.
The issue happens in this line below, which I will explain.
$FileList = $FileList.Add($C)
The List.Add() method's return type is either a void or an integer (it's only an int when you provide a generic object, if you provide the type specified for the list, in your case being your custom class of File_Information, it provides a VOID.)
Void is the same as null. We can confirm that the output of .Add is the same as $null with the following code.
PS> $FileList.Add($c) -eq $null
True
You can see the output for the various ways of calling .Add() in detail by just typing out the method name with no parameters, like this.
PS> $FileList.Add
OverloadDefinitions
-------------------
void Add(File_Information item) #< the one you're using!
void ICollection[File_Information].Add(File_Information item)
int IList.Add(System.Object value)
So in effect your for each loops are adding an item to the list, then resaving the variable $FileList to the output of the .Add method, which is a void, or null in other words. You're killing your own list!
We don't need to resave $FileList as we go, since the purpose of a list is to be populated in code, and the only thing we have to do is to call the .Add() method.
Fix: stop killing your list object in your loops by changing these lines in your code.
#before
$FileList = $FileList.Add($C)
#after
$FileList.Add($C)

Alphanumerical sorting not working in Array

We have a directory, which features many subdirectories (one per day) with serveral files in it. Unfortunately, files can be resent - so a file of 2020-01-01 can be resend (with slightly different filename, since a timestamp is added to the filename) on 2020-02-03. The structure looks something like this:
TopDir
20200801
AFile_20200801_20200801150000 (Timestamped 2020-08-01 15:00:00)
BFile_20200801_20200801150000
CFile_20200801_20200801150000
20200802
AFile_20200802_20200801150000
BFile_20200802_20200801150000
CFile_20200802_20200801150000
AFile_20200801_20200802150000 (Timestamped 2020-08-02 15:00:00)
So the AFile of 2020-08-01 has been resent on 2020-08-02 at 3 PM.
I am now trying to retrieve a list with the most recent file per day, so I built up an array and populated it with all files below TopDir (recurively). So far so good, all files are found:
$path = "Y:\";
$FileArray = #()
$FileNameArray = #()
$FileArrayCounter = 0
foreach ($item in Get-ChildItem $path -Recurse)
{
if ($item.Extension -ne "")
{
$StringPart1, $StringPart2, $StringPart3, $StringPart4 = $item.Name.Split('_');
$FileNameShort = "{0}_{1}_{2}" -f $StringPart1.Trim(), $StringPart2.Trim(), $StringPart3.Trim();
$FileNameShort = $FileNameShort.Trim().ToUpper();
$FileArray += #{FileID = $FileArrayCounter; FileNameShort = $FileNameShort; FileName = $item.Name; FullName = $item.FullName; LastWriteTime = $item.LastWriteTime};
$FileArrayCounter ++;
}
}
$FileArray = $FileArray | sort FileNameShort; ##{Expression={"FileNameShort"}; Ascending=$True} #, #{Expression={"LastWriteTime"}; Descending=$True}
foreach($f in $FileArray)
{
Write-host($f.FileNameShort, $f.LastWriteTime)
}
Write-host($FileArrayCounter.ToString() + " Dateien gefunden");
The newly added column "FileNameShort" includes a substring of the filename. With this done, I receive two Rows for AFile_20200801:
AFile_20200801, AFile_20200801_20200801150000, ...
AFile_20200801, AFile_20200801_20200802150000, ...
However, when I try to sort my array (see above code), the output is NOT sorted by name. Instead I receive something like the following:
AFile_20200801
CFile_20200802
AFile_20200801
BFile_20200801
What I want to achieve is a sorting by FileNameShort ASCENDING and LastWriteTime DESCENDING.
What am I missing here?
Your sort does not work because $FileArray is an array of hash tables. The syntax Sort FileNameShort is binding the FileNameShort property to the -Property parameter of Sort-Object. However, the hash table does not contain a property called FileShortName. You can see this if you run $FileArray[0] | Get-Member.
If you create them as custom objects, the simple sort syntax works.
$FileArray += [pscustomobject]#{FileID = $FileArrayCounter; FileNameShort = $FileNameShort; FileName = $item.Name; FullName = $item.FullName; LastWriteTime = $item.LastWriteTime}
$FileArray | Sort FileNameShort # This will sort correctly
As an aside, I do not recommend using += to seemingly add elements to an array. It is best to either output the results inside of your loop and save the loop results or create a list with an .Add() method. The problem with += is the current array is expanded into memory and those contents are then used to create a new array with the new items. As the array grows, it becomes increasingly non-performant. See below for a more efficient example.
$FileArray = foreach ($item in Get-ChildItem $path -Recurse)
{
if ($item.Extension -ne "")
{
$StringPart1, $StringPart2, $StringPart3, $StringPart4 = $item.Name.Split('_');
$FileNameShort = "{0}_{1}_{2}" -f $StringPart1.Trim(), $StringPart2.Trim(), $StringPart3.Trim();
$FileNameShort = $FileNameShort.Trim().ToUpper();
# Outputting custom object here
[pscustomobject]#{FileID = $FileArrayCounter; FileNameShort = $FileNameShort; FileName = $item.Name; FullName = $item.FullName; LastWriteTime = $item.LastWriteTime};
$FileArrayCounter ++;
}
}
I just found the solution:
$FileArray = $FileArray | sort #{Expression={[string]$_.FileNameShort}; Ascending=$True}, #{Expression={[datetime]$_.LastWriteTime}; Descending=$True}
Still I don't know, why the first sorting did not work as expected.

Iterate through XML nodes / objects and pass to variables in Powershell

I'd be wanting to iterate through a set of XML and then pass those to variables which can be printed.
Here is an example of the data:
<applications>
<size>75</size>
<application>
<name>Applications 1</name>
<path>/Applications/Utilities/Application 1</path>
<version>10.14</version>
</application>
<application>
<name>Application 2</name>
<path>/Applications/Utilities/Application 2</path>
<version>6.3.9</version>
</application>
</applications
I've looked at using ForEach-Object when trying to output it but to no avail.
[string]$applicationProperties = $API.applications.application| ForEach-Object {
$_.name
$_.path
$_.version
}
This works but puts them all on one line, I'd like them so they print on individual lines but I couldn't prefix the $_ variable. I'm new to POSH as you can tell.
e.g. so I'd like to have name/path/version data saved to variables
[string]$applicationProperties = $API.applications.application | ForEach-Object {
[string]$name_var = $_.name
[string]$path_var = $_.path
[string]$version_var = $_.variable
}
This gives me one "application", but not all the possible objects. Also mentions that even when I'm putting down $name_var it's not accessing that variable? Do I need to do something to access that variable?
Any advice would be appreciated.
When you assign the output from ForEach-Object to [string]$applicationProperties, you're forcing PowerShell to convert all the strings into a single string because of the cast to [string].
What you'll want to do is create a new object for each application node that you're iterating over:
$appInformation = $API.applications.application | ForEach-Object {
# Create a new custom objects with the values from the XML nodes
[pscustomobject]#{
Name = $_.name
Path = $_.path
Version = $_.version
}
}
Now $appInformation will contain an array of objects each with a Name, Path and Version property. You can then further use and/or manipulate these objects in your scripts rather than just having a bunch of strings:
$appInformation |ForEach-Object {
Write-Host "Here is the version of application '$($_.Name)': $($_.Version)"
}
If you want to see them printed in the console with each property value on a separate line just pipe the array to Format-List:
$appInformation |Format-List

How to use a string value in foreach?

How to use a string value in foreach?
The following works.
$printString='$_.name+","+$_.name'
Get-ChildItem|foreach {$_.name+','+$_.name}
But the following doesn't work
Get-ChildItem|foreach {$printString}
But I need it to work: because I have a task to print each column in a table, I can use table dictionary to get all the columns, so all are dynamic, and then when I try to print the result, I also use a string like above to print the result. Any solution
There are several solutions. Some of them that came on my mid are:
$printString='$($_.name),$($_.name)'
Get-ChildItem | % { $ExecutionContext.InvokeCommand.ExpandString($printString) }
$formatString='{0},{0}'
Get-ChildItem | % { $formatString -f $_.Name }
$s = {param($file) $file.Name + "," + $file.Name }
Get-ChildItem | % { & $s $_ }
The first one expands string and that's probably what you wanted. Note that composed variables have to be enclosed in $(..). The second just formats some input. The third uses scriptblock there you can create any string you want (the most powerfull)
One possible solution:
$printString={$_.name+","+$_.name}
Get-ChildItem |foreach {.$printString}
Another possible solution:
$printString='$_.name+","+$_.name'
Get-ChildItem|foreach { Invoke-Expression $printString }
Interesting possibility:
dir | select #{Name='Name'; Expression={$_.Name, $_.Name}}

Get last element of pipeline in powershell

This might be weird, but stay with me.
I want to get only the last element of a piped result to be assigned to a varaiable.
I know how I would do this in "regular" code of course, but since this must be a one-liner.
More specifically, I'm interested in getting the file extension when getting the result from an FTP request ListDirectoryDetails.
Since this is done within a string expansion, I can't figure out the proper code.
Currently I'm getting the last 3 hars, but that is real nasty.
New-Object PSObject -Property #{
LastWriteTime = [DateTime]::ParseExact($tempDate, "MMM dd HH:mm",[System.Globalization.CultureInfo]::InvariantCulture)
Type = $(if([int]$tempSize -eq 0) { "Directory" } else { $tempName.SubString($tempName.length-3,3) })
Name = $tempName
Size = [int]$tempSize
}
My idea was doing something similar to
$tempName.Split(".") | ? {$_ -eq $input[$input.Length-1]}
that is, iterate over all, but only take out where the element I'm looking at is the last one of the input-array.
What am I missing ?
A few ways to do this:
$name = 'c:\temp\aaa.bbb.ccc'
# way 1
$name.Split('.') | Select-Object -Last 1
# way 2
[System.IO.Path]::GetExtension($name)
# or if the dot is not needed
[System.IO.Path]::GetExtension($name).TrimStart('.')
In general, getting the last element in the pipeline would be done using Select -Last 1 as Roman suggests above. However, an alternate and easier way to do this if the input is a simple array is to use array slicing e.g.:
PS> $name = "c:\temp\aaa.bbb.txt"
PS> $name.Split('.')[-1]
txt
Was your intent to get the file's extension or basename? Because it seems that the Type property already contains the extension for the filename.