I'm trying to work out how to create objects, set fields for that object, and then add the object to a collection.
Specifically, how can I create a $newPerson where the Name field is "joe" and with an array consisting of "phone1, phone2, phone3"? Similarly, "sue" has an array of "cell4 etc" and "alice" with her attributes, and so on. Ultimately, to put these three objects into an array of objects, $collectionOfPeople?
output:
thufir#dur:~/flwor/csv$
thufir#dur:~/flwor/csv$ pwsh import.ps1
people name
joe name
phone1 attribute
phone2 attribute
phone3 attribute
sue name
cell4 attribute
home5 attribute
alice name
atrib6 attribute
x7 attribute
y9 attribute
z10 attribute
thufir#dur:~/flwor/csv$
code:
$tempAttributes = #()
$collectionOfPeople = #()
function attribute([string]$line) {
Write-Host $line "attribute"
$tempAttributes += $line
}
function name([string]$line) {
Write-Host $line "name"
#is a $newPerson ever instantiated?
$newPerson = [PSCustomObject]#{
Name = $line
Attributes = $tempAttributes
}
$newPerson #empty? no output
$collectionOfPeople += $newPerson
$tempAttributes = #()
}
$output = switch -regex -file people.csv {
'\d' { attribute($_) ; $_ }
default { name($_); $_ }
}
#how to read the file from top to bottom?
#[array]::Reverse($output)
#$output
$collectionOfPeople #empty???
input from a CSV file:
people
joe
phone1
phone2
phone3
sue
cell4
home5
alice
atrib6
x7
y9
z10
In the above CSV there's no marker between records, so I'm using an if statement on the assumption that every attribute has digits while the names never have digits.
You could do something like the following, which keeps closely to your current logic. This does assume that the order of data in your file is exactly as you have stated. .
switch -regex -file people.csv {
'\d' { $Attributes.Add($_) }
default {
if ($Attributes -and $Name) {
[pscustomobject]#{Name = $Name; Attributes = $Attributes}
}
$Name = $_
$Attributes = [Collections.Generic.List[string]]#()
}
}
# Output Last Set of Attributes
[pscustomobject]#{Name = $Name; Attributes = $Attributes}
# Cleanup
Remove-Variable Name
Remove-Variable Attributes
If there are no attributes for a name, that name is ignored. That feature can be changed by adding a condition elseif ($Name) { [pscustomobject]#{Name = $Name; Attributes = $null} } within the default block.
Related
I am using a config file that contains some information as shown below.
User1:xyz#gmail.com
User1_Role:Admin
NAME:sdfdsfu4343-234324-ffsdf-34324d-dsfhdjhfd943
ID:xyz#abc-demo-test-abc-mssql
Password:rewrfsdv34354*fds*vdfg435434
I want to split each value from*: to newline* in my Powershell script.
I am using -split '[: \n]' it matches perfectly until there is no '' in the value. If there is an '*' it will fetch till that. For example, for Password, it matches only rewrfsdv34354. Here is my code:
$i = 0
foreach ($keyOrValue in $Contents -split '[: *\n]') {
if ($i++ % 2 -eq 0) {
$varName = $keyOrValue
}
else {
Set-Variable $varName $keyOrValue
}
}
I need to match all the chars after : to \n. Please share your ideas.
It's probably best to perform two separate splits here, it makes things easier to work out if the code is going wrong for some reason, although the $i % 2 -eq 0 part is a neat way to pick up key/value.
I would go for this:
# Split the Contents variable by newline first
foreach ($line in $Contents -split '[\n]') {
# Now split each line by colon
$keyOrValue = $line -split ':'
# Then set the variables based on the parts of the colon-split
Set-Variable $keyOrValue[0] $keyOrValue[1]
}
You could also convert to a hashmap and go from there, e.g.:
$h = #{}
gc config.txt | % { $key, $value = $_ -split ' *: *'; $h[$key] = $value }
Or with ConvertFrom-StringData:
$h = (gc -raw dims.txt) -replace ':','=' | ConvertFrom-StringData
Now you have convenient access to keys and values, e.g.:
$h
Output:
Name Value
---- -----
Password rewrfsdv34354*fds*vdfg435434
User1 xyz#gmail.com
ID xyz#abc-demo-test-abc-mssql
NAME sdfdsfu4343-234324-ffsdf-34324d-dsfhdjhfd943
User1_Role Admin
Or only keys:
$h.keys
Output:
Password
User1
ID
NAME
User1_Role
Or only values:
$h.values
Output:
rewrfsdv34354*fds*vdfg435434
xyz#gmail.com
xyz#abc-demo-test-abc-mssql
sdfdsfu4343-234324-ffsdf-34324d-dsfhdjhfd943
Admin
Or specific values:
$h['user1'] + ", " + $h['user1_role']
Output:
xyz#gmail.com, Admin
etc.
I've been given the task of searching for SSNs (and other PII so we can remove it) in our entire file structure, fun I know. So far this script will search thru all .xlsx files in a given directory, but no matter what I try, I cannot for the life of me get the $SearchText variable to work. I have tried so many different deviations of the regex currently displayed, the only regex string that works is straight question marks; "???????????", but that returns entires I'm not looking for.
Any help would be very much appreciated.
Thanks!
$SourceLocation = "C:\Users\nick\Documents\ScriptingTest"
$SearchText2 = "^(?!(000|666|9))\d{3}-(?!00)\d{2}-(?!0000)\d{4}$"
$SearchText = "*"
$FileNames = Get-ChildItem -Path $SourceLocation -Recurse -Include *.xlsx
Function Search-Excel {
$Excel = New-Object -ComObject Excel.Application
$Workbook = $Excel.Workbooks.Open($File)
ForEach ($Worksheet in #($Workbook.Sheets)) {
$Found = $WorkSheet.Cells.Find($SearchText)
If ($Found.Text -match "SearchText2") {
$BeginAddress = $Found.Address(0,0,1,1)
[pscustomobject]#{
WorkSheet = $Worksheet.Name
Column = $Found.Column
Row =$Found.Row
Text = $Found.Text
Address = $File
}
Do {
$Found = $WorkSheet.Cells.FindNext($Found)
$Address = $Found.Address(0,0,1,1)
If ($Address -eq $BeginAddress) {
BREAK
}
[pscustomobject]#{
WorkSheet = $Worksheet.Name
Column = $Found.Column
Row =$Found.Row
Text = $Found.Text
Address = $File
}
} Until ($False)
}
}
}
$workbook.close($false)
[void][System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$excel)
[gc]::Collect()
[gc]::WaitForPendingFinalizers()
Remove-Variable excel -ErrorAction SilentlyContinue
foreach ($File in $FileNames)
{
Search-Excel
}
EDIT: Turns out excel has a very limited range of acceptable regex: Acceptable Excel Regex,
so I modified the first $Searchtext viarable to just be "*", and the first if statement to match regex outside of excel's search. Now I just need to come up with a crafty regex pattern to filter what I want. The next problem is filtering:
No letters.
Valid SSNs with dashes.
Valid SSNs without dashes. (this part is stumping me, how to search for something that can have dashes, but if it doesn't, it can only be 9 characters long)
It definitely doesn't appear to be regex, but this did work with the dashes. The issues I see with your code is
You define the searchtext outside of the function and don't pass it in
Same with the file names
Your workbook close, com release, gc, etc is outside of your function, so it won't do anything. (except maybe error?)
Here is what I got to work with your code. Now if you have other text that matches the pattern of 3 chars dash 2 chars dash 4 chars, you can easily filter those out afterwards with regex or whatever you like.
$SourceLocation = "C:\Users\nick\Documents\ScriptingTest"
$SearchText = "???-??-????"
$FileNames = Get-ChildItem -Path $SourceLocation -Recurse -Include *.xlsx
Function Search-Excel {
[cmdletbinding()]
Param($File,$SearchText)
$Excel = New-Object -ComObject Excel.Application
$Workbook = $Excel.Workbooks.Open($File)
ForEach ($Worksheet in #($Workbook.Sheets)) {
$Found = $WorkSheet.Cells.Find($SearchText)
If ($Found) {
$BeginAddress = $Found.Address(0,0,1,1)
[pscustomobject]#{
WorkSheet = $Worksheet.Name
Column = $Found.Column
Row =$Found.Row
Text = $Found.Text
Address = $File
}
Do {
$Found = $WorkSheet.Cells.FindNext($Found)
$Address = $Found.Address(0,0,1,1)
If ($Address -eq $BeginAddress) {
BREAK
}
[pscustomobject]#{
WorkSheet = $Worksheet.Name
Column = $Found.Column
Row =$Found.Row
Text = $Found.Text
Address = $File
}
} Until ($False)
}
}
$workbook.close($false)
[void][System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$excel)
[gc]::Collect()
[gc]::WaitForPendingFinalizers()
Remove-Variable excel -ErrorAction SilentlyContinue
}
foreach ($File in $FileNames)
{
Write-Host processing $File.fullname
Search-Excel -File $File.fullname -SearchText $SearchText
}
Output from test file
WorkSheet : Sheet1
Column : 2
Row : 5
Text : 123-12-5555
Address : C:\temp\excel2.xlsx
WorkSheet : Sheet1
Column : 3
Row : 21
Text : 586-99-3844
Address : C:\temp\excel2.xlsx
WorkSheet : Sheet1
Column : 7
Row : 28
Text : 987-65-4321
Address : C:\temp\excel2.xlsx
There are about ten lines of data. For each line of data I want to indicate whether that line contains numerals.
How can I print out "yes, this line has numerals" or "no, this line has no numerals" for each and every line, exactly once?
output:
thufir#dur:~/flwor/csv$
thufir#dur:~/flwor/csv$ pwsh import.ps1
no digits
Name
----
people…
thufir#dur:~/flwor/csv$
code:
$text = Get-Content -Raw ./people.csv
[array]::Reverse($text)
$tempAttributes = #()
$collectionOfPeople = #()
ForEach ($line in $text) {
if($line -notmatch '.*?[0-9].*?') {
$tempAttributes += $line
Write-Host "matches digits"
}
else {
Write-Host "no digits"
$newPerson = [PSCustomObject]#{
Name = $line
Attributes = $tempAttributes
}
$tempAttributes = #()
$collectionOfPeople += $newPerson
}
}
$collectionOfPeople
data:
people
joe
phone1
phone2
phone3
sue
cell4
home5
alice
atrib6
x7
y9
z10
The only reason I'm printing "digits" or "no digits" is as a marker to aid in building the object.
You can use the following:
switch -regex -file people.csv {
'\d' { "yes" ; $_ }
default { "no"; $_ }
}
\d is a regex character matching a digit. A switch statement with -regex allows for regex expressions to be used for matching text. The default condition is picked when no other condition is met. $_ is the current line being processed.
switch is generally faster than Get-Content for line by line processing. Since you do want to perform certain actions per line, you likely don’t want to use the -Raw parameter because that will read in all file contents as one single string.
# For Reverse Output
$output = switch -regex -file people.csv {
'\d' { "yes" ; $_ }
default { "no"; $_ }
}
$output[($output.GetUpperBound(0))..0)]
I'm using Powershell to write a script which based on an input file splits each section and manipulates the underlying data.
In practical terms, i have a TXT file which is formatted like so:
-------Group_Name_1-------
member: Name:Host1 Handle:123
member: Name:Host2 Handle:213
member: Name:Host3 Handle:321
-------Group_Name_2-------
member: Name:Host10 Handle:1230
member: Name:Host20 Handle:2130
Group (member of): Firewall subnet
etc...
So far, I've come up with the following script:
$filepath = 'C:\test.txt'
$getfile = Get-Content $filepath -Raw
$regex_group_name = '.*-------'
foreach($object in $splitfile){
$lines = $object.Split([Environment]::NewLine)
$data_group_name = #($lines) -match $regex_group_name
$data_group_value = $data_group_name -replace '-------' , ''
Write-Host $data_group_value
}
I'm trying to achieve the following output for each group (for example):
add group name Group_Name_1 members.1 "Host1" members.2 "Host2" members.3 "Host3" groups.1 "Firewall Subnet"
And I'm stuck in 2 parts:
1) The script above, obviously won't work, because the "split" function will basically remove the group name: how can I approach this problem in order to get the desired output?
2) I have no idea on how to change the output in order to reflect the effective number of members (if the members are 2, it will only output members.1 and members.2; alternatively members.1, members.2, members.3 etc...).
Thanks in advance for your help.
I would solve this in two steps.
First, parse the file into a data structure (in this case, an array of PSCustomObjects)
Then, convert those into the command strings.
Assuming this $fileContent
$fileContent = "-------Group_Name_1-------
member: Name:Host1 Handle:123
member: Name:Host2 Handle:213
member: Name:Host3 Handle:321
-------Group_Name_2-------
member: Name:Host10 Handle:1230
member: Name:Host20 Handle:2130
Group (member of): Firewall subnet
etc..."
We can build step 1 like this:
$sections = $fileContent -split "(?m)^-------"
$groups = foreach ($section in $sections) {
$group = [pscustomobject]#{
Name = ([regex]"(.*)-------").Match($section).Groups[1].Value
Members = #(([regex]"member: Name:(\S+)").Matches($section) | ForEach-Object {
$_.Groups[1].Value
})
MemberOf = #(([regex]"Group \(member of\): (.+)").Matches($section) | ForEach-Object {
$_.Groups[1].Value
})
}
if ($group.Name) {
$group
}
}
# intermediate results (parsed group structure)
$groups
This prints
Name Members MemberOf
---- ------- --------
Group_Name_1 {Host1, Host2, Host3} {}
Group_Name_2 {Host10, Host20} {Firewall subnet}
Now it's easy to turn this into a different format:
$commands = foreach ($group in $groups) {
$str = "add group "
$str += "name `"$( $group.Name )`" "
$i = 1
foreach ($member in $group.Members) {
$str += "members.$i `"$member`" "
$i++
}
$i = 1
foreach ($memberOf in $group.MemberOf) {
$str += "groups.$i `"$memberOf`" "
$i++
}
$str.Trim()
}
# final results (command strings)
$commands
Which prints:
add group name "Group_Name_1" members.1 "Host1" members.2 "Host2" members.3 "Host3"
add group name "Group_Name_2" members.1 "Host10" members.2 "Host20" groups.1 "Firewall subnet"
i'm using the next function to parse one ini file.
I have one problem that i don't know how to solve:
If one match is found, break loop and read next line.
I know that i can put break to do this. But, break exit of all switch code. ¿?
I suppose it's a special scenario when using switch with regex to parse a file.
¿How can i do to not parse same line in two code blocks?
See in the output:
`Comment ; Parameters that are strings are specified parameter=string, like this:`
`Key ; Parameters that are strings are specified parameter=string, like this:`
and
`Comment ; Parameters that are numbers are specified parameter=number, like this:`
`Key ; Parameters that are numbers are specified parameter=number, like this:`
Thanks
function Get-IniContent ($filePath)
{
$ini = #{}
switch -regex -file $FilePath
{
"^\[(.+)\]$" # Section
{
$section = $matches[1]
$ini[$section] = #{}
$CommentCount = 0
Write-Host "Section $section"
}
"^(;.*)$" # Comment
{
if (!($section))
{
$section = "No-Section"
$ini[$section] = #{}
}
$value = $matches[1]
$CommentCount = $CommentCount + 1
$name = "Comment" + $CommentCount
$ini[$section][$name] = $value
Write-Host "Comment $value"
}
"(.+?)\s*=\s*(.*)" # Key
{
if (!($section))
{
$section = "No-Section"
$ini[$section] = #{}
}
$name,$value = $matches[1..2]
$ini[$section][$name] = $value
Write-Host "Key $name=$value"
}
default
{
# Next line causes NullArray error
#$line=$matches[1]
Write-Host "Strange line $line"
}
}
return $ini
}
$iniFile=Get-IniContent (Join-Path (Split-Path -parent $MyInvocation.MyCommand.Path) "test.config")
This it's a sample ini file:
Independent Bad Line
timer=19
[Common]
; 4 - default value hard-coded in ntfs-hardlink-backup.ps1 script
; Blank lines also do no harm
; Parameters that are strings are specified parameter=string, like this:
backupDestination=X:\Backup
; Parameters that are numbers are specified parameter=number, like this:
backupsToKeep=20
[server-01.mycompany.example.org]
; Parameters that are specific to a particular server/computer go in a section for that computer.
; The section name is the fully-qualified domain name (FQDN) of the computer.
backupSources=D:\Shares\Admin,E:\Shares\Finance,E:\Shares\ICT,D:\Shares\Users
:Bad Line
=10 ; Also Bad Line
And this it's script output:
E:\Config\NtfsBackup>powershell -ExecutionPolicy unrestricted -file "E:\Config\NtfsBackup\nt1.ps1"
Strange line
Key timer=19
Section Common
Comment ; 4 - default value hard-coded in ntfs-hardlink-backup.ps1 script
Strange line
Comment ; Blank lines also do no harm
Strange line
Comment ; Parameters that are strings are specified parameter=string, like this:
Key ; Parameters that are strings are specified parameter=string, like this:
Key backupDestination=X:\Backup
Comment ; Parameters that are numbers are specified parameter=number, like this:
Key ; Parameters that are numbers are specified parameter=number, like this:
Key backupsToKeep=20
Strange line
Section server-01.mycompany.example.org
Comment ; Parameters that are specific to a particular server/computer go in a section for that computer.
Comment ; The section name is the fully-qualified domain name (FQDN) of the computer.
Key backupSources=D:\Shares\Admin,E:\Shares\Finance,E:\Shares\ICT,D:\Shares\Users
Strange line
Strange line
The answer is simple. You do not want to use Break but instead use Continue which will stop the loop for the current item and start the loop for the next item. Placing Continue at the end of all of your script blocks (probably on a new line just after the Write-Host line) for the switch will do what you need.
function Get-IniContent ($filePath)
{
$ini = #{}
switch -regex -file $FilePath
{
"^\[(.+)\]$" # Section
{
$section = $matches[1]
$ini[$section] = #{}
$CommentCount = 0
Write-Host "Section $section"
Continue
}
"^(;.*)$" # Comment
{
if (!($section))
{
$section = "No-Section"
$ini[$section] = #{}
}
$value = $matches[1]
$CommentCount = $CommentCount + 1
$name = "Comment" + $CommentCount
$ini[$section][$name] = $value
Write-Host "Comment $value"
Continue
}
"(.+?)\s*=\s*(.*)" # Key
{
if (!($section))
{
$section = "No-Section"
$ini[$section] = #{}
}
$name,$value = $matches[1..2]
$ini[$section][$name] = $value
Write-Host "Key $name=$value"
Continue
}
default
{
# Next line causes NullArray error
#$line=$matches[1]
Write-Host "Strange line $line"
}
}
return $ini
}
$iniFile=Get-IniContent (Join-Path (Split-Path -parent $MyInvocation.MyCommand.Path) "test.config")