PowerShell - need ini file for consecutive numbering - file

I'm currently working on a script for automation. This script should have a global count variable that does not reset itself when the script is executed again. Therefore, I need a configuration file that stores this count variable and uses it when it is called up again. This counting variable is also dependent on an ID. There is therefore a count variable for each ID. The configuration file can be in XML or INI format. Can someone tell me how to create such a file the easiest way and how to add IDs or get the count variable? I dont think "csv-import/export" is the right way.
I've already tried this...
$results = #()
$details = #{
Key1 = $ID
Key2 = $count
Key3 = "sth"
Key4 = "sth"
Key5 = "sth"
}
$results += New-Object PSObject -Property $details
$results | export-csv -Path C:\Users\...\configure.txt -NoTypeInformation
Unfortunately, I can't get any further here, because it overwrites the previous entry every time the ID changes and I don't know how to add additional entries (if the ID already exists), update entries (count variable) and call this count variable to use it in Powershell.
Anybody got a suggestion?
Best Regards

You can use a hash table, Export-CliXml and Import-CliXml to save and load you ID counts to a XML file:
$xmlFilePath = 'idCounts.xml'
# If the XML file exists, it is loaded
if( Test-Path -Path $xmlFilePath -PathType Leaf )
{
$hashTable = Import-Clixml -Path $xmlFilePath
}
# Else a new hash table is initialized
else
{
$hashTable = #{}
}
# Set the count of ID '001' to 1
$hashTable['001'] = 1
# Increment the count of ID '002'
$hashTable['002'] += 1
# Save the hash table to the XML file
$hashTable | Export-Clixml -Path $xmlFilePath

Thank you for all the tips. In the end, I managed it myself in the following way:
if(!((import-csv "C:\Users\...\Desktop\ini.txt") | where-object {$_.Key1 -eq $ID}))
{
$results = #()
$details = #{
Key 1 = $ID
Key 2 = 1
Key 3 = "something"
Key 4 = "something"
Key 5 = "something"
Key 6 = "something"
}
$results += New-Object PSObject -Property $details
$results | export-csv -Path C:\Users\...\Desktop\ini.txt -append -NoTypeInformation
}
The system first checks whether there is an entry with the corresponding ID. If not, an object is created that has that ID. The count variable is set to 1 when it is newly created. The entry is attached to the file with "Export CSV".
$select = (import-csv "C:\Users\...\Desktop\ini.txt" | where{$_.Key1 -eq $ID})
[int]$global:number = [convert]::ToInt32($select.Key2)
To use the count variable, the configuration file is imported. I have set it to "global" because it has to operate over several functions.
($csv = Import-Csv "C:\Users\...\Desktop\ini.txt") | ForEach {
if ($_.Key1 -eq $ID) {
$_.Key2 = $global:number}
}
$csv | Export-Csv "C:\Users\...\Desktop\ini.txt" -NoTypeInformation
At the end, the count variable is updated and transferred back to the file with "Export CSV".
Nevertheless thank you for all the interesting suggestions!
Best Regards

Related

Add Column to CSV file Using Other CSV file

I am working with two CSV files. One holds the name of users and the other one holds their corresponding email address. What I want to do is to combine them both so that users is column 1 and email is column 2 and output it to one file. So far, I've managed to add a second column from the email csv file to the user csv file, but with blank row data. Below is the code that I am using:
$emailCol= import-csv "C:\files\temp\emailOnly.csv" | Select-Object -skip 1
$emailArr=#{}
$i=0
$nameCol = import-csv "C:\files\temp\nameOnly.csv"
foreach ($item in $emailCol){
$nameCol | Select *, #{
Name="email";Expression=
{$emailArr[$i]}
} | Export-Csv -path
C:\files\temp\revised.csv -NoTypeInformation
}
Updated: Below is what worked for me. Thanks BenH!
function combineData {
#This function will combine the user CSV file and
#email CSV file into a single file
$emailCol = Get-Content "C:\files\temp\emailOnly.csv"
| Select-Object -skip 1
$nameCol = Get-Content "C:\files\temp\nameOnly.csv" |
Select-Object -skip 1
# Max function to find the larger count of the two
#csvs to use as the boundary for the counter.
$count = [math]::Max($emailCol.count,$nameCol.count)
$CombinedArray = for ($i = 0; $i -lt $count; $i++) {
[PSCustomObject]#{
fullName = $nameCol[$i]
email = $emailCol[$i]
}
}
$CombinedArray | Export-Csv C:\files\temp\revised.csv
-NoTypeInformation
}
To prevent some additional questions about this theme let me show you alternative approach. If your both CSV files have same number of lines and each line of the first file corresponds to the first line of the second file and etc. then you can do next. For example, users.csv:
User
Name1
Name2
Name3
Name4
Name5
and email.csv:
Email
mail1#gmail.com
mail2#gmail.com
mail3#gmail.com
mail5#gmail.com
Our purpose:
"User","Email"
"Name1","mail1#gmail.com"
"Name2","mail2#gmail.com"
"Name3","mail3#gmail.com"
"Name4",
"Name5","mail5#gmail.com"
What we do?
$c1 = 'C:\path\to\user.csv'
$c2 = 'C:\path\to\email.csv'
[Linq.Enumerable]::Zip(
(Get-Content $c1), (Get-Content $c2),[Func[Object, Object, Object[]]]{$args -join ','}
) | ConvertFrom-Csv | Export-Csv C:\path\to\output.csv
If our purpose is:
"User","Email"
"Name1","mail1#gmail.com"
"Name2","mail2#gmail.com"
"Name3","mail3#gmail.com"
"Name5","mail5#gmail.com"
then:
$c1 = 'C:\path\to\user.csv'
$c2 = 'C:\path\to\email.csv'
([Linq.Enumerable]::Zip(
(Get-Content $c1), (Get-Content $c2),[Func[Object, Object, Object[]]]{$args -join ','}
) | ConvertFrom-Csv).Where{$_.Email} | Export-Csv C:\path\to\output.csv
Hope this helps you in the future.
A for loop would be better suited for your loop. Then use the counter as the index for each of the arrays to build your new object.
$emailCol = Get-Content "C:\files\temp\emailOnly.csv" | Select-Object -Skip 2
$nameCol = Get-Content "C:\files\temp\nameOnly.csv" | Select-Object -Skip 1
# Max function to find the larger count of the two csvs to use as the boundary for the counter.
$count = [math]::Max($emailCol.count,$nameCol.count)
$CombinedArray = for ($i = 0; $i -lt $count; $i++) {
[PSCustomObject]#{
Name = $nameCol[$i]
Email = $emailCol[$i]
}
}
$CombinedArray | Export-Csv C:\files\temp\revised.csv -NoTypeInformation
Answer edited to use Get-Content with an extra skip added to skip the header line in order to handle blank lines.

Export array with "sub-array"

I am currently trying to automate license counting in Office 365 across multiple partner tenants using PowerShell.
My current code (aquired from the internet) with some modifications gives me this output:
Column A Column B Column C
-------- -------- --------
CustA LicA,LicB 1,3
CustB LicA,LicB 7,3
CustC LicA 4
But the output I want from this code is:
Column A Column B Column C
-------- -------- --------
CustA LicA 1
LicB 3
CustB LicA 7
LicB 3
Here is my current code which is exported using Export-Csv -NoType:
$tenantID = (Get-MsolPartnerContract).tenantid
foreach($i in $tenantID){
$tenantName = Get-MsolPartnerInformation -TenantId $i
$tenantLicense = Get-MsolSubscription -TenantId $i
$properties = [ordered]#{
'Company' = ($tenantName.PartnerCompanyName -join ',')
'License' = ($tenantLicense.SkuPartNumber -join ',')
'LicenseCount' = ($tenantLicense.TotalLicenses -join ',')
}
$obj = New-Object -TypeName psobject -Property $properties
Write-Output $obj
}
I have tried this along with several other iterations of code which all fail catastophically:
$properties = [ordered]#{
'Company' = ($tenantName.PartnerCompanyName -join ','),
#{'License' = ($tenantLicense.SkuPartNumber -join ',')},
#{'LicenseCount' = ($tenantLicense.TotalLicenses -join',')}
}
I was thinking about making a "sub-array" $tenantLicense.SkuPartnumber and $tenantLicense.TotalLicenses, but I'm not quite sure how to approach this with appending it to the object or "main-array".
A second loop for each $tenantLIcense should do the trick for you. I don't have access to an environment like yours so I cannot test this.
$tenantID | ForEach-Object{
$tenantName = Get-MsolPartnerInformation -TenantId $_
$tenantLicense = Get-MsolSubscription -TenantId $_
# Make an object for each $tenantLicense
$tenantLicense | ForEach-Object{
$properties = [ordered]#{
'Company' = $tenantName.PartnerCompanyName
'License' = $_.SkuPartNumber
'LicenseCount' = $_.TotalLicenses
}
# Send the new object down the pipe.
New-Object -TypeName psobject -Property $properties
}
}
Since you have multiple $tenantLicenses that have the same company name lets just loop over those and use the same company name in the output. Assuming this worked it would not have the same output as you desired since there no logic to omit company in subsequent rows. I would argue that is it better this way since you can sort the data now with out loss of data / understanding.
Notice I change foreach() to ForEach-Object. This makes it simpler to send object down the pipe.
Without providing the code solution I can say that you need to build up the array.
In terms of programming, you will need to iterate the array ARRAY1 and populate another one ARRAY2with the extra rows. For example if columns A,B are simple value and C is an array of 3 items, then you would add 3 rows in the new table with A,B,C1, A,B,C2 and A,B,C3. On each iteration of the loop you need to calculate all the permutations, for example in your case the ones generated by columnB and columnC.
This should be also possible with pipelining using the ForEach-Object cmdlet but that is more difficult and as you mentioned your relatively new relationship with powershell I would not pursuit this path, unless of coarse you want to learn.

Powershell Custom object - not passing foreach variable

I'm trying to create a custom object based on server names from a text file.
The script I have goes and imports the txt file into a Variable. Then runs a foreach server in the servers variable to create the custom object. I would like to be able to output the object's properties as a table that doesn't include the header info each time.
See script and output below:
$SERVERS = gc c:\servers.txt
foreach ($srv in $SERVERS)
{
$Obj = New-Object PsObject -Property`
#{
Computername = $srv
SecurityGroup = (Get-QADComputer $srv).memberof
RebootDay = ((Get-QADComputer $srv).memberof).split(',').split(' ')[2]
Combined = ((Get-QADComputer $srv).memberof).split(',').split(' ').split('=')[1]
RebootTime = $obj.combined.substring(0,4)
}
echo $obj | ft Computername,RebootDay -autosize
}
This is the output currently:
Computername RebootDay
SERVER007 Sunday
Computername RebootDay
SERVER009 Sunday
Computername RebootDay
SERVER003 Sunday
I'd like it to look more like:
Computername RebootDay
SERVER007 Sunday
SERVER001 Sunday
SERVER009 Sunday
TessellatingHeckler was on the right track really. The issue with his code is that you can't pipe a ForEach($x in $y){} loop to anything (not to be confused with a ForEach-Object loop that you usually see shortened to just ForEach like $Servers | ForEach{<code here>}) You don't want to pipe objects to Format-Table one at a time, you want to pipe a collection of objects to it so that it looks nice. So here's the modified code:
$SERVERS = gc c:\servers.txt
$Results = foreach ($srv in $SERVERS)
{
New-Object PsObject -Property #{
Computername = $srv
SecurityGroup = (Get-QADComputer $srv).memberof
RebootDay = ((Get-QADComputer $srv).memberof).split(',').split(' ')[2]
Combined = ((Get-QADComputer $srv).memberof).split(',').split(' ').split('=')[1]
RebootTime = $obj.combined.substring(0,4)
}
}
$Results | FT ComputerName,RebootDay -auto
That collects the objects in an array, then you pass the whole array to Format-Table
Don't put the "ft" (Format-Table) command inside the loop, put it outside, once, at the end. e.g.
$SERVERS = gc c:\servers.txt
$results = foreach ($srv in $SERVERS)
{
$Obj = New-Object PsObject -Property`
#{
Computername = $srv
SecurityGroup = (Get-QADComputer $srv).memberof
RebootDay = ((Get-QADComputer $srv).memberof).split(',').split(' ')[2]
Combined = ((Get-QADComputer $srv).memberof).split(',').split(' ').split('=')[1]
RebootTime = $obj.combined.substring(0,4)
}
$Obj
}
$results | ft Computername,RebootDay -autosize
Edit: Fixed for foreach pipeline bug.
You could possibly neaten it a bit because you don't need to make a new PSObject for a hashtable, and then put the object into the pipeline; you don't need to repeat the Get-QADComputer commands three times. I'm suspicious that the $obj.combined line isn't doing anything - how can you refer to an object inside the properties of the new-object call, before it gets assigned that name? And the repeated splits could probably be combined because it operates on individual characters, not strings.
gc c:\servers.txt | foreach {
$memberof = (Get-QADComputer $_).memberof
#{
Computername = $_;
SecurityGroup = $memberof;
RebootDay = $memberof.split(', ')[2];
Combined = $memberof.split(', =')[1];
# ?? RebootTime = $obj.combined.substring(0,4)
}
} | ft Computername,RebootDay -autosize

PowerShell ForEach loop returning only One Result

I feel like perhaps I am overlooking something simple here, but I am having trouble with a ForEach loop in PowerShell actually returning all items that I expect. I have a script that will query an Oracle database and gather up the base data set. Once this is gathered, I will need to perform some adjustments to what is returned and build an additional bit of information (not in the script currently, working through the basics so far)
What I am doing is adding the data to an array, then trying to use a ForEach loop to examine each item in the array and pump that data out to another array that will have the new properties that I need to populate based on some calculations of the base data set. What I am getting returned to the variable $finaloutput is only one line of the data (for the example I am posting here I simply look for one reportnumber equal to CPOD-018, which there are 5 of in the data set with varying other properties populated including the sitename which I am populate as well, but still only get one result).
I've tried going about this using nested if statements within the ForEach loop instead of the piped Where-Object, but received the same results. Below is the current version of the script, any assistance would be greatly appreciated.
param(
[parameter(mandatory=$True)]$username,
[parameter(mandatory=$True)]$password
)
# setup the finaloutput variable
$finaloutput = New-Object psobject
$finaloutput | Add-Member -MemberType NoteProperty -name ReportNumber -value NotSet
$finaloutput | Add-Member -MemberType NoteProperty -name sitename -value NotSet
# the connection string to be used by the OlEDB connection
$connString = #"
Provider=OraOLEDB.Oracle;Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST="host.host.host")(PORT="1521"))
(CONNECT_DATA=(SERVICE_NAME="name.name.name")));User ID="$username";Password="$password"
"#
# the query that will be used to gather data from Oracle
$qry= #"
select VP_EXPECTED_DETAILS.REPORTNUMBER, VP_EXPECTED_DETAILS.SITE_NAME, VP_ACTUAL_FILENAME_DETAILS.FILE_NAME, VP_EXPECTED_DETAILS.MAX_EXPECTED_LOAD_TIME, VP_EXPECTED_DETAILS.EXPECTED_FREQUENCY,
VP_EXPECTED_DETAILS.DATE_TIMING, VP_EXPECTED_DETAILS.FREQUENCY_DAY, VP_EXPECTED_DETAILS.JOB_NO,
TO_CHAR(VP_ACTUAL_RPT_DETAILS.GEN_PARSE_IN,'YYYYMMDDHH24MISS') AS GEN_PARSE_IN,
TO_CHAR(VP_ACTUAL_RPT_DETAILS.GEN_PARSE_OUT,'YYYYMMDDHH24MISS') AS GEN_PARSE_OUT,
TO_CHAR(VP_ACTUAL_RPT_DETAILS.ETLLOADER_IN,'YYYYMMDDHH24MISS') AS ETLLOADER_IN,
TO_CHAR(VP_ACTUAL_RPT_DETAILS.ETLLOADER_OUT,'YYYYMMDDHH24MISS') AS ETLLOADER_OUT
from MONITOR.VP_EXPECTED_DETAILS
LEFT JOIN MONITOR.VP_ACTUAL_RPT_DETAILS on VP_EXPECTED_DETAILS.REPORTNUMBER = VP_ACTUAL_RPT_DETAILS.REPORTNUMBER and VP_EXPECTED_DETAILS.SITE_NAME = VP_ACTUAL_RPT_DETAILS.SITE_NAME
LEFT JOIN MONITOR.VP_ACTUAL_FILENAME_DETAILS on VP_ACTUAL_RPT_DETAILS.FNKEY = VP_ACTUAL_FILENAME_DETAILS.FNKEY where VP_EXPECTED_DETAILS.EXPECTED_FREQUENCY = 'DAILY' or
(VP_EXPECTED_DETAILS.EXPECTED_FREQUENCY = 'MONTHLY' AND VP_EXPECTED_DETAILS.FREQUENCY_DAY = EXTRACT(DAY from SYSDATE))
"#
# the function that will open the database connection and execute the query
function Get-OLEDBData ($connectstring, $sql) {
$OLEDBConn = New-Object System.Data.OleDb.OleDbConnection($connectstring)
$OLEDBConn.open()
$readcmd = New-Object system.Data.OleDb.OleDbCommand($sql,$OLEDBConn)
$readcmd.CommandTimeout = '300'
$da = New-Object system.Data.OleDb.OleDbDataAdapter($readcmd)
$dt = New-Object System.Data.DataTable
[void]$da.fill($dt)
$OLEDBConn.close()
return $dt
}
# populate $output with the data from the Get-OLEDBData function
$output = Get-OLEDBData $connString $qry
# build the final output that will generate alerts
ForEach ($lines in $output | Where-Object {$_.reportnumber -eq "CPOD-018"})
{
$finaloutput.reportnumber = $lines.reportnumber
$finaloutput.sitename = $lines.SITE_NAME
}
$finaloutput
It looks like what is happening is you are doing the object creation incorrectly. Inside the foreach loop you keep overwriting the same values rather than appending new objects
It should look more like this:
$FinalOutput = ForEach ($lines in $output | Where-Object {$_.reportnumber -eq "CPOD-018"})
{
$Prop = #{
'reportnumber' = $lines.reportnumber
'sitename' = $lines.SITE_NAME
}
New-Object -Type PSObject -Property $Prop
}
$FinalOutput
You would need to comment out the finaloutput lines earlier in your script.

One element containing hashes instead of multiple elements - how to fix?

I am trying to parse robocopy log files to get file size, path, and date modified. I am getting the information via regex with no issues. However, for some reason, I am getting an array with a single element, and that element contains 3 hashes. My terminology might be off; I am still learning about hashes. What I want is a regular array with multple elements.
Output that I am getting:
FileSize FilePath DateTime
-------- -------- --------
{23040, 36864, 27136, 24064...} {\\server1\folder\Test File R... {2006/03/15 21:08:01, 2010/12...
As you can see, there is only one row, but that row contains multiple items. I want multiple rows.
Here is my code:
[regex]$Match_Regex = "^.{13}\s\d{4}/\d{2}/\d{2}\s\d{2}:\d{2}:\d{2}\s.*$"
[regex]$Replace_Regex = "^\s*([\d\.]*\s{0,1}\w{0,1})\s(\d{4}\/\d{2}\/\d{2}\s\d{2}:\d{2}:\d{2})\s(.*)$"
$MainContent = New-Object System.Collections.Generic.List[PSCustomObject]
Get-Content $Path\$InFile -ReadCount $Batch | ForEach-Object {
$FileSize = $_ -match $Match_Regex -replace $Replace_Regex,('$1').Trim()
$DateTime = $_ -match $Match_Regex -replace $Replace_Regex,('$2').Trim()
$FilePath = $_ -match $Match_Regex -replace $Replace_Regex,('$3').Trim()
$Props = #{
FileSize = $FileSize;
DateTime = $DateTime;
FilePath = $FilePath
}
$Obj = [PSCustomObject]$Props
$MainContent.Add($Obj)
}
$MainContent | % {
$_
}
What am I doing wrong? I am just not getting it. Thanks.
Note: This needs to be as fast as possible because I have to process millions of lines, which is why I am trying System.Collections.Generic.List.
I think the problem is that for what you're doing you actually need two foreach-object loops. Using Get-Content with -Readcount is going to give you an array of arrays. Use the -Match in the first Foreach-Object to filter out the records that match in each array. That's going to give you an array of the matched records. Then you need to foreach through that array to create one object for each record:
[regex]$Match_Regex = "^.{13}\s\d{4}/\d{2}/\d{2}\s\d{2}:\d{2}:\d{2}\s.*$"
[regex]$Replace_Regex = "^\s*([\d\.]*\s{0,1}\w{0,1})\s(\d{4}\/\d{2}\/\d{2}\s\d{2}:\d{2}:\d{2})\s(.*)$"
$MainContent =
Get-Content $Path\$InFile -ReadCount $Batch |
ForEach-Object {
$_ -match $Match_Regex |
ForEach-Object {
$FileSize = $_ -replace $Replace_Regex,('$1').Trim()
$DateTime = $_ -replace $Replace_Regex,('$2').Trim()
$FilePath = $_ -replace $Replace_Regex,('$3').Trim()
[PSCustomObject]#{
FileSize = $FileSize
DateTime = $DateTime
FilePath = $FilePath
}
}
}
You don't really need to use the collection as an accumulator, just output PSCustomObjects, and let them accumulate in the result variable.

Resources