I understand that custom objects are created like this :
$obj=new-object psobject
and then I understand that you can add members (and values) like this :
$obj | Add-Member noteproperty name Jeff
Now the question is, how do you populate the object, add and remove "rows" of values ?
The only way around I found is to create an array and then push objects inside it, like this :
$array = #()
$array+=new-object PSObject -Property #{Name="Jeff"}
$array+=new-object PSObject -Property #{Name="Joe"}
$array+=new-object PSObject -Property #{Name="John"}
etc..
Is there a straight forward way to "increment" the values of the members in an object ?
$obj+=(Name=John)
doesn't work.
Thanks
A very late response, but I hope it helps someone who needs to count objects.
Let's start with a list of users we wish to count.
> $users = 1..10 | % {New-object psobject -Property #{ Name = "User $_"; Age = $_ } }
> $users
Age Name
--- ----
1 User 1
2 User 2
3 User 3
4 User 4
5 User 5
6 User 6
7 User 7
8 User 8
9 User 9
10 User 10
To count them, put them into a hash table of counters
> # Create hash table
> $counter = #{}
> # Assign users as keys in the table
> $users | % { $counter.Add($_, 0) }
> $counter
Name Value
---- -----
#{Age=4; Name=User 4} 0
#{Age=1; Name=User 1} 0
#{Age=3; Name=User 3} 0
#{Age=5; Name=User 5} 0
#{Age=10; Name=User 10} 0
#{Age=9; Name=User 9} 0
#{Age=8; Name=User 8} 0
#{Age=7; Name=User 7} 0
#{Age=6; Name=User 6} 0
#{Age=2; Name=User 2} 0
Then you can increment the counter whenever you encounter
the user in your script. For example, to increment "User 1" twice
and "User 4" once
> $counter[$users[0]] += 1
> $counter[$users[0]] += 1
> $counter[$users[3]] += 1
> $counter
Name Value
---- -----
#{Age=4; Name=User 4} 1
#{Age=1; Name=User 1} 2
#{Age=3; Name=User 3} 0
#{Age=5; Name=User 5} 0
#{Age=10; Name=User 10} 0
#{Age=9; Name=User 9} 0
#{Age=8; Name=User 8} 0
#{Age=7; Name=User 7} 0
#{Age=6; Name=User 6} 0
#{Age=2; Name=User 2} 0
In your example above, I believe you end up with a System.Management.Automation.PSCustomObject, not an array. I use something similar to what you're doing when I build reports that contain custom objects. If you're really just using that to store one property, though, it's probably overkill. You could just do something like this:
$names += "John"
$names += "Fred"
In the case that you really want to add new note properties to an object throughout a script, this is how I do it. Keep in mind PowerShell doesn't like to add note properties with the same name, so if you do that you'll have to simply set the property with =
Here's an example of what I do:
$params += #{Name = $_.Name}
$params += #{Calculation = $someCalculatedValue}
$collection += New-Object -Type PSObject -Property $params
Related
I need to get the occurrence of "failure" in a logfile.
The thing is I need to get the occurrence of "failure" for each session block.
What the log looks like:
---Session 1
check
check
failure
failure
check
----Session 2
check
failure
check
check
----Session 3
failure
failure
What I've got so far is this:
$rows = Get-Childitem -Path E:\shell\lot.log |
Select-String -Pattern failure
$i = 0
foreach ($row in $rows) {
$i++
}
echo $i
With that script I only get the total of the occurrences.
I would start a new counter whenever a line beginning with 3 or more consecutive hyphens occurs and collect the results in a hashtable.
$failcount = #{}
Get-Content 'E:\shell\lot.log' | ForEach-Object {
if ($_ -match '^-{3,}(.*)') {
$section = $matches[1].Trim()
$failcount[$section] = 0
} elseif ($_ -like '*failure*') {
$failcount[$section]++
}
}
I believe this will do it. Key part is to reset your counter after each session.
$rows = Get-Childitem -Path E:\shell\lot.log
$i = 0 # failure counter
$j = 1 # session counter
foreach($row in $rows){
if($row -like "*Session 1"){
# skip the first line. Edit: removed * as would skip session 10, 11 etc. assumes line ends with "session 1"
continue
}elseif($row -eq "failure){
# if the row is equal to failure, count it
$i++
}elseif($row -like "*Session*"){
# when you get to the end of a session, print the number of failures
Write-Host "Session $j had $i failures"
# ... and reset the failure counter
$i = 0
# increment the session counter
$j++
}
}
I'll add another option. Read the whole log file in with -Raw to get a multi-line string. Remove the ---- from the first line, and then split on 3 or more hyphens at the beginning of a line, this gets you each session as a multi-line string, then you could just output text or custom objects, or whatever you wanted with it. Split the multi-line string on new line characters, filter for 'failure', and do a count to get failures per session.
(GC E:\shell\lot.log -Raw) -replace '^-+' -split '(?<=[\r\n])---+'|%{
'{0} had {1} failure(s)' -f ($_.split("`n")[0].Trim()),($_ -split '[\r\n]+'|?{$_ -match 'failure'}).Count
}
That would (given the sample provided) output:
Session 1 had 2 failure(s)
Session 2 had 1 failure(s)
Session 3 had 2 failure(s)
I currently have a spreadsheet with the following display:
+------------+---------+---------+
| Site | UserP |UserName |
+------------+---------+---------+
| Site A | Read | user1 |
| | | |
+------------+---------+---------+
| Site A | Write | user2 |
| | | |
+------------+---------+---------+
The problem is that sometimes the UserName field brings a Group and then I have to open this group and create a new row for each user in this group with the same information.
So if I had this:
Site A | Read | Group 1
It would become this:
Site A | Read | Group 1
Site A | Read | Group 1 User A
Site A | Read | Group 1 User B
Site A | Read | Group 1 User C
I can manually pull the information from the group, the thing is that I don't know how to read a specific column from excel and then create a new row in that excel sheet.
I didn't post any code cause I barely have one. I don't know how to loop to the cells.
This is what I have.
$file = "\Desktop\AccessReviewReportPRE.csv"
$sheetName = "AccessReviewReportPRE"
$objExcel = New-Object -ComObject Excel.Application
$workbook = $objExcel.Workbooks.Open($file)
$sheet = $workbook.Worksheets.Item($sheetName)
Now, I know how to access individual cells, like this:
$worksheet.cells.Item(3, 3).text
Just not sure how to loop trough all rows, check what is in the third column and do a action on that.
Following #BenH tip, I did this:
$file = "\Desktop\AccessReviewReportPRE.csv"
$fileContent = Import-csv $file -header "URL", "Site/List/Folder/Item", "Title/Name", "PermissionType", "Permissions", "LoginName"
$newRow = New-Object PsObject -Property #{"URL" = 'sdfs'; "Site/List/Folder/Item" = 'dsfd'; "Title/Name" = 'sdf'; "PermissionType" = 'dsf'; "Permissions" = 'sdfs'; "LoginName" = 'sdf'}
$fileContent += $newRow
$fileContent | Export-Csv -NoTypeInformation -Path "\Desktop\AccessReviewReportAFTER.csv"
But it is duplicating the header. Any ideas why?
To loop through each Row you need a loop.
I recommend a do-while.
#My Code to find a groupname
$WorkBook=$Excel.Workbooks.Open($strPath)
#Selcet the first Tabelle
$mappe = $workbook.sheets.item(1)
$Target = $mappe.Range("A1").Find($Grouname)
$start = $Target.Row
end, $middle, $counter = 0
do {
#assigning
$middle = $end
#Search Funktion
$nextFind = $mappe.Range("A1:A150000").FindNext($Target)
#Old Range assinging to $find
$Target = $nextFind
#Assign the Rownumber
$end = $nextFind.Row
#Check if the Value is correct
if ($mappe.cells.Item($nextFind.Row,1).Value() -eq $Groupname)
{
$counter++
}else{
break
}
}while($end -gt $start)
#The FindNext function only works when there are at least 2 of the groups
#but in some cases there is only 1 group. To fix the issue a counter counts
#the passages and if it is 1, $middle will be assigned the start row.
if($counter -eq 1)
{
$middle= $start
}
#Because a line must be added after the last one, $row needs be increased by 1
$row = $middle + 1
#Column stays the same, later it will be increased
$column = $Target.Column
# ADD ROW
$eRow = $mappe.cells.item($row,1).entireRow
$active = $eRow.activate()
$active = $eRow.insert()
With this Code, I'm looking for a Group. The Finde function which I use helps me to locate the group. The finde function does not work in a loop, that's why you must use the FindeNext function.
$middle contains the last row number to add a row it must be increased.
In order to add data into multiple columns, the row has to stay the same, but the column must be increased.
$mappe.Cells.Item($row, $column) = Text
$column++
If you wanna check the 3 column and based on its value you wanna make an action, make a if-statment.
I know the Code ain't perfect, but I hope it helped.
I got a script which export to csv some AD attributes
I want to have the last 3 characters of the 'initials' attribute in a PsObject but i have an error and i spent Hours on this...
Could you help me ?
The Error:
*Method invocation failed because [Microsoft.ActiveDirectory.Management.ADUser] doesn't contain a method named 'substring'.
At C:\scripts\ExtractDWH\Untitled2.ps1:15 char:26
+ "Test" = $_.substring <<<< ($_.initials.length - 3, 3)
+ CategoryInfo : InvalidOperation: (substring:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound*
The Code :
$output = 'c:\scripts\ExtractDWH\consultants_test.csv'
$users = Get-ADUser -Filter * -SearchBase "ou=Rennes,ou=Consultants,ou=Utilisateurs,ou=FedFinance,dc=dfedinterim,dc=fr" - Properties * | ? { ($_.initials -notlike 'IC*') -and ($_.initials -notlike 'IM*') -and ($_.initials -ne $null) }
fileIn | % {
$users | % {
New-Object psobject -Property #{
"ID" = $_.initials
"Last 3 strings ID" = $_.substring($_.initials.length - 3, 3)
"Centre Imputation" = $_.extensionAttribute10
"Date Entrée" = $_.extensionAttribute9
# The line below does not work
"Test" = $_.substring($_.initials.length - 3, 3)
}
}
} | Select-Object ID, 'Centre Imputation', 'Date Entrée', 'Test'
| Export-CSV $output -Delimiter ';' -Encoding "UTF8" -NoTypeInformation `
Thank you !
Change this:
"Last 3 strings ID" = $_.substring($_.initials.length - 3, 3)
To This:
"Last 3 strings ID" = $_.initials.substring($_.initials.length -3, 3)
Avshalom showed you the error. You can also use the following to access the last 3 characters:
"Test" = $_.initials[-3 .. -1] -join ''
This question already has an answer here:
Dynamically create variables in PowerShell
(1 answer)
Closed 7 years ago.
I am trying to assign / divide a string out to assign them to variables. The length of each row is random so I am kinda at a loss. Thanks for the help.
-String-
Company 1
Employee 1
Employee 2
Employee 3
Company 2
Employee 1
Employee 2
Employee 3
I want to assign each line to a variable. $a = "company 1" $b = "employee 1" ect. The only stable variable in this string is each line of data is a different variable. The length of each line is unknown.
Thanks again!
If you really need to create a distinct variable for each row of your string, then use New-Variable in a loop:
$str = 'Company 1
Employee 1
Employee 2
Employee 3
Company 2
Employee 1
Employee 2
Employee 3'
# to know how many variables were created we will put them into array
$variables = #()
$i = 0
foreach ($row in ($str -split [Environment]::NewLine)) {
# create new variable from row's value
New-Variable -Name "row$i" -Value $row
# and create a member of array
$variables += [pscustomobject]#{Name = "row$i"; Value = $row}
$i++
}
Write-Host "Total: $i"
foreach ($var in $variables) {
Write-Host $var.Name $var.Value
}
# now we can get value from a variable
Write-Host $row1
# or from array
Write-Host $variables[1].Name $variables[1].Value
But to operate on a collection of items it is really simplier to use arrays and other collection types without creating a variable per item:
$rows = #()
foreach ($row in ($str -split [Environment]::NewLine)) {
$rows += $row
}
Write-Host "Total rows: $rows.Count"
# get the first row
$rows[0]
Today I have just thrown together this PowerShell script which
takes a tab-delimited text file,
reads it into memory,
makes a variable number of filter queries based on distinct values of a certain column
creates a new empty Excel workbook
adds each of the subsets of filtered data to
a new Excel worksheet
The last step is where I am stuck. Currently my code puts a few lines of data into a range in the worksheet, in the form of unrolled/transposed "key: value" entries, resulting in a horizontal data layout. The same range of data is always overwritten.
I want data in the form of a vertical layout, i.e., data in columns, just the same way as if the CSV file was imported with the import-file-wizard of MS Excel.
Is there a simpler way to do it than below?
I admit, some of the PowerShell features are pasted in here in a cargo-cult mode of programming. Please note that I have no PowerShell experience whatsoever. I did some batchfile, VBScript, and VBA coding a few years back. So, other criticisms are also welcome.
PARAM (
[Parameter(ValueFromPipeline = $true)]
$infile = ".\04-2011\110404-13.txt"
)
PROCESS {
echo " $infile"
Write-Host "Num Args:" $args.Length;
$xl = New-Object -comobject Excel.Application;
$xl.Visible = $true;
$Workbook = $xl.Workbooks.Add();
$content = Import-Csv -delimiter "`t" $infile;
$ports = $content | Select-Object Port# | Sort-Object Port# -Unique -Descending;
$ports | ForEach-Object {
$p = $_;
Write-Host $p.{Port#};
$Worksheet = $Workbook.Worksheets.Add();
$workSheet.Name = [string]::Format("{0} {1}", "PortNo", $p.{Port#});
$filtered = $content | Where-Object {$_.{Port#} -eq $p.{Port#} };
$filtered | ForEach-Object {
Write-Host $_.{ObsDateTime}, $_.{Port#}
}
$filtered | clip.exe;
$range = $Workbook.ActiveSheet.Range("a2", "a$($filtered.count)");
$Workbook.ActiveSheet.Paste($range, $false);
}
$xl.Quit()
}
Data Output Example
Wrong
Port# : 1
Obs# : 1
Exp_Flux : 0,99
IV Cdry : 406.96
IV Tcham : 16.19
IV Pressure : 100.7
IV H2O : 9.748
IV V3 : 11.395
IV V4 : 0.759
IV RH : 53.12
Right
Port# Obs# Exp_Flux IV Cdry IV Tcham IV Pressure IV H2O IV V3 IV V4 IV RH
1 1 0,99 406.96 16.19 100.7 9.748 11.395 0.759 53.12
Try Export-Xls, it looks very nice. Never had the chance to use it, but (virtually) knowing the person who worked on it, I'm sure you will be very happy to use it. If you'll go with it, please provide a feedback here will be appreciated.
POSSIBLE WORKAROUND FOR UNORDERED PROPERTIES IN Export-Xls
The function Add-Array2Clipboard could be changed so that it accepts a new input parameter: an array providing the name of the properties ordered as required.
Then the you can change the section where get-member is used. Silly example:
"z", "a", "c" | %{ get-member -name $_ -inputobject $thecurrentobject }
This is just an example on how you can achieve ordered properties from get-member.
I've used the $Workbook.ActiveSheet.Cells.Item($row, $col).Value2 function to more be able to pinpoint more precisely where to put the data when exporting to Excel.
Something like
$row = 1
Get-Content $file | Foreach-Object {
$cols = $_.split("`t")
for ($i = 0; $i < $cols.count; $i++)
{
$Workbook.ActiveSheet.Cells.Item($row, $i+1).Value2 = $cols[$i]
}
$row++
}
Warning: dry-coded! You'll probably need some try..catch as well.
I used a modified Export-Xls function, a bit different as User empo suggested.
This is my call to it
Export-Xls $filtered -Path $outfile -WorksheetName "$wn" -SheetPosition "end" | Out-Null # -SheetPosition "end";
However, the current release of Export-Xls re-orders the columns of the in-memory representation of the csv-text -file. I want the data columns of the text file in their original order, so I had to hack and simplify the original code as follows:
function Add-Array2Clipboard {
param (
[PSObject[]]$ConvertObject,
[switch]$Header
)
process{
$array = #();
$line =""
if ($Header) {
$line = #()
$row = $ConvertObject | Select -First 1
$row.psobject.properties | Foreach {$line += "$($_.Name)" }
$array += [String]::Join("`t", $line)
}
else {
foreach($row in $ConvertObject){
$line =""
$vals = #()
$row.psobject.properties | Foreach {$vals += $_.Value}
$array += [String]::Join("`t", $vals)
}
}
$array | clip.exe;
}
}