pull text between characters - powershell

How do I pull text between two words? I know regex can do this and I have been looking around but the code I try just does not work for me at all...as clueless as a brick with regex...so probably am doing it totally wrong...
I have a text file and want to query whatever is displayed between these text strings:
[Problem Devices]
Device PNP Device ID Error Code
[USB]
I tried doing this but getting no where!
$devices = Get-Content c:\temp\dev.txt | out-string [regex]::match($devices,'(?<=\<Problem Devices\>).+(?=\<USB\>)',"singleline").value.trim()
You cannot call a method on a null-valued expression.
At line:1 char:141
+ $devices = Get-Content c:\temp\dev.txt | out-string [regex]::match($devices,'(?<=\<Problem Devices\>).+(?=\<USB\>)',"
singleline").value.trim <<<< ()
+ CategoryInfo : InvalidOperation: (trim:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull

Piping to out-string is not needed; get-content is sending each line of the file into the pipeline as a separate object. So you want to iterate through those objects with a foreach-object.
$devices = Get-Content c:\temp\dev.txt | foreach-object{[regex]::match($devices,'(?<=\<Problem Devices\>).+(?=\<USB\>)',"singleline").value.trim()}
However, you are still left with the problem of attempting to trim() a null object - if your regex match doesn't find a match, you can't call value.trim().
Your regex tries to match on <Problem Devices> when your input file has [Problem Devices].
Rather than try to do everything in a single set of pipeline steps, break your problem down:
For each line in the file, check for [Problem Devices]
For each subsequent line, if it is [USB], exit the loop. If it is not [USB], capturing each line into a variable (build an array of these lines)
After the loop, iterate over each element of the array you just built to parse each value out (creating a collection of PSObjects (one per device), or a collection of hashes (one per device), depending on your needs).

If you're not comfortable with regex, there are other ways:
$test = $false
$devices = get-content file.txt |
foreach {
if ($_.trim() -eq '[Problem Devices]'){$test = $true}
elseif ($_.trim() -eq '[USB]') {$test = $false}
elseif ($test){$_}
} | where {$_.trim()}

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)

Powershell Adding items from text file to 2 dimensional array

I am used to coding in java and I am brand new to Powershell. I have a text file that contains Windows server info displayed like this.
14.0.3026.27,None,CU7,4229789,SQL Server 2017,0
14.0.3025.34,None,CU6,4101464,SQL Server 2017,0
14.0.3023.8,None,CU5,4092643,SQL Server 2017,0
I am trying to throw this info into a 2 dimensional array and want it to look like this.
[14.0.3026.27],[None],[CU7],[4229789],[SQL Server 2017],[0]
[14.0.3025.34],[None],[CU6],[4101464],[SQL Server 2017],[0]
[14.0.3023.8],[None],[CU5],[4092643],[SQL Server 2017],[0]
The code I have is giving this error message:
Cannot index into a null array. At line:9 char:9
+ $array[$i][$j] = $word
+ ~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : NullArray
Here is my code:
$file = Get-Content "C:\Users\UserName\Desktop\sqlServers.txt"
$array = #(), #()
$i = 0
$j = 0
foreach ($line in $file){
$j=0
foreach ($word in $line.split(",")){
$array[$i][$j] = $word
$j+=1
}
$i+=1
}
PowerShell (and .NET) arrays are fixed size so assigning to an element beyond the array bounds won't grow the array. Instead, let PowerShell build the arrays for you. The following will produce what you want (an array of arrays btw, not an actual 2-d array)
$result = get-content data.txt | foreach { , ($_ -split ',')}
In this code, reading the data will give you the rows, splitting the rows will give you the columns. The trick is the comma before the split operation. Without it, all of the elements would be streamed into a single flat array. The comma preserves the nested array so you get the desired array of arrays.
As your file is comma separated (it's a CSV with a .txt extension) you could instead use Import-Csv to create the array.
You will need to manually specify headers as you example input doesn't include them.
Code with example headers:
$array = Import-Csv "C:\folder\sqlServers.txt" -Header Version,Something,CU,Number,Product,Another
You can then reference the items by index and property name:
PS > $array[0].CU
CU7
PS > $array[2].Product
SQL Server 2017

Not able to call out array headers in a foreach loop

Okay - this is a really weird issue - and it probably has a really stupid solution.
But i import a csv
$csv = import-csv c:\users\hello.csv
Then i have an array of words, for which i am wanting to use to search through the csv - and if there's a match in the csv - populate an adjacent column in the csv.
here's the array:
$newhandles
hi
hello
now - if i run a foreach loop with an if statement inside of it - it doesn't recognize one of the headers.
foreach ($newhandle in $newhandles)
{if ($csv.name -eq $newhandle) {$csv.redundant = $newhandle}}
however it gives me this error:
The property 'redundant' cannot be found on this object. Verify that the property exists
and can be set.
At line:1 char:69
+ ... andles) {if ($csv.name -eq $newhandle) {$csv.redundant = $newhandle}}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : PropertyAssignmentException
I know the property exists - because if i call it directly - it shows three empty slots - and if i call something to it directly - the element will populate. such as
> $csv[0].redundant = 'hi'
> $csv[0]
name : hi
description : don't settle
system : sight
redundant : hi
tags :
Any ideas?
try using this foreach loop :
foreach ($rec in $csv){
if($newhandles -contains $rec.name){
$rec.redundant = $rec.name
}
}
if you check ($csv.redundant).GetType(),you can see that it returns an array instead of the property you want but when you are assigning value to $csv[0].redundant you are accessing the exact property and that's why it works when you tested manually
try this
import-csv "c:\users\hello.csv" | select * , #{N="redundant";E={if ($newhandles -contains $_.Name) {$_.Name} else {$null}}} -ExcludeProperty redundant

Create Out-File-names using array elements

I need to create .txt/.sap files/shortcuts with changing content. I use a do until loop and the file names should be created with strings from an array. I cannot use the square brackets to access the array because Powershell interprets them as a wild card characters.
The following code shows the principle:
$strSAPSystems = #("Production", "Finance", "Example")
$i = 0
do{
"text1" | Out-File .\SAP_$strSAPSystems[$i].sap
"text2" | Out-File .\SAP_$$strSAPSystems[$i].sap -Append
$i++
}
until($i -eq $strSAPSystems.length)
results in an error: "out-file : cannot perform operation because the wildcard path ... did not resolve to a file"
I tried to add the -literalPath parameter but it didn't work. I am new to Powershell, is there a better way to have the files named after the SAP systems?
Thank you
You need to wrap the string(path) inside a subexpression $(.....) to extract the value of a single element in an array. Atm. the path becomes something like .SAP_Production Finance Example[$i].sap.
Also, you have an extra $ in the second Out-File. Personally I would rewrite everthing to:
$strSAPSystems = #("Production", "Finance", "Example")
$strSAPSystems | ForEach-Object {
"text1" | Out-File ".\SAP_$($_).sap"
"text2" | Out-File ".\SAP_$($_).sap -Append"
}
$_ is the current item in the array, and since it's a single object, I don't really need the subexpression $(), but I included it because it easier to see static and dynamic parts of the path.

Trying to run legacy executables from powershell script

I am looking to run net.exe from a script and I am having some trouble with spaces. Here is the code...
# Variables
$gssservers = Import-Csv "gssservers.csv"
$gssservers | Where-Object {$_.Tier -match "DB"} | Foreach-Object {
net.exe use "\\"$_.Name '/user:'$_.Name'\Administrator' $_.Pass
$sqlcheck = sc.exe \\$gsssql[1] query "WUAUSERV"
}
When I set line 5 to Write-Host I see that there are spaces that are added outside of anywhere I have quotes which is breaking the net.exe command. How can I remove those spaces?
For anyone questioning how I am doing this, the net.exe command is the only way I can get to these machines as WMI is blocked in this enclave.
My first guess is that you've got "invisible" spaces in your CSV file. For example their is likely a trailing whitespace after the names of your servers in the CSV that your eyes of course don't see. You can fix that either by fixing the CSV file, or using .Trim() on your imported strings -- i.e. $_.Name.Trim()
If that's not the case, or not the only issue, then this is something I've had issues with to. When I have complicated strings like your desired net.exe arguments I've liked to take precautions and get extra pedantic with defining the string and not rely on PowerShell's automatic guessing of exactly where a string begins and ends.
So, instead of baking your parameters inline on your net.exe command line hand-craft them into a variable first, like so
$args = '\\' + $_.name + '/user:' + $_.name + '\Administrator' + $_.pass
If you write-Host that out you'll see that it no longer has your rogue spaces. Indeed you may notice that it no longer has enough spaces, so you'll have to get a little explicit about where they belong. For instance the above line doesn't put the proper spaces between \\servername and /user, or between the username and password, so you'd have to add that space back in, like so.
$args = '\\' + $_.name + ' /user:' + $_.name + '\Administrator ' + $_.pass
Notice the explicit spaces.
I finally resolved this myself using #EdgeVB's solution. The code ended up like this...
# Variables
$gssservers = Import-Csv "gssservers.csv"
$gssservers | Where-Object {$_.Tier -match "DB"} | Foreach-Object {
$cmd1 = 'use'
$arg1 = '\\' + $_.Name
$arg2 = ' /user:' + $_.Name + '\Administrator '
& net.exe $cmd1 $arg1 $arg2 $_Pass
$cmd2 = 'query'
$svc1 = 'mssqlserver'
& sc.exe $arg1 $cmd2 $svc1 | Write-Host
}
Not only do you need to bake the variables in beforehand, but they also cannot cross certain thresholds (for instance, if "use" and "\" are in the same variable, it breaks.