Powershell Foreach loop in INI file - powershell

I am actually new in Powershell scripting with inni files and I need to create a foreach loop that allows me to get multiple variables.
Let me explain :
I have an INI files that contains severals groups (Active Directory groups), I need my powershell script to extract those groups that have names like (group1, group2, group3 and so on).
I tried this piece of code but nothing goes out.
$i=0
$query = $configfile.Domain.group.$i
ForEach($query in $configfile.Domain.group)
{
$i++
write-host $query.$i
}
Looks like i'm pretty bad, any advices ?

The $query in the loop is declaring a new variable which hides the variable declared outside the loop.
Without an example of the ini file, I can only guess, but you could try replacing the loop block with write-host $query.
This will at least show you what is going on.

If your INI file looks anything like this:
[Groups]
group1=example
group2=example1
group3=group1
group4=example3
group5=group2
group6=example5
then you can use the function from here like this:
function Get-IniContent ($filePath) {
$ini = #{}
switch -regex -file $FilePath {
"^\[(.+)\]" # Section
{
$section = $matches[1]
$ini[$section] = #{}
$CommentCount = 0
}
"^(;.*)$" # Comment
{
$value = $matches[1]
$CommentCount = $CommentCount + 1
$name = "Comment" + $CommentCount
$ini[$section][$name] = $value
}
"(.+?)\s*=(.*)" # Key
{
$name,$value = $matches[1..2]
$ini[$section][$name] = $value
}
}
return $ini
}
# the data returned is a Hashtable where each entry is listed
# under a certain section name. This data is UNORDERED by default
$data = Get-IniContent -filePath 'D:\Test\groups.ini'
Get all group names found in the ini file under section 'Groups'
$data['Groups'].Keys | ForEach-Object { $data['Groups'][$_]}
Output:
example
example5
group2
group1
example1
example3
Or get only certain group names that look like 'group' for instance
($data['Groups'].GetEnumerator() | Where-Object { $_.Value -like 'group*' }).Value
# or alternatively do:
# $data['Groups'].Keys | Where-Object { $data['Groups'][$_] -like 'group*' } | ForEach-Object { $data['Groups'][$_] }
Output:
group2
group1
If you want the found group names sorted, append | Sort-Object

Related

Powershell : Find a group of text in a file then extract a specific line in that group of text

I've been on this for few days now, I'm trying to parse multiple text files containing data like this :
[Cluster1]
GatewayIp=xx.xxx.xxx.xx
IpAddress=xx.xxx.xxx.x
MTU=0000
NetMask=xxx.xxx.xxx.0
Port=xxx
Protocol=xxxx/xxxxx
Sessions=xxxxxx
Bands=xxx, xxx, x
Binding=xxxxx
GroupNumber=x
InitQueue=xxxxxx
Interface=xxxxxx
Process=xxx
SupportsCar=No
SupportsCom=Yes
SupportsPos=Yes
SupportsXvd=No
[Cluster2]
GatewayIp=xx.xxx.xxx.xx
IpAddress=xx.xxx.xxx.x
MTU=0000
NetMask=xxx.xxx.xxx.0
Port=xxx
Protocol=xxxx/xxxxx
Sessions=xxxxxx
Bands=xxx, xxx, x
Binding=xxxxx
GroupNumber=x
InitQueue=xxxxxx
Interface=xxxxxx
Process=xxx
SupportsCar=No
SupportsCom=No
SupportsPos=No
SupportsXvd=Yes
I want to extract the "IpAddress" in the section where thoses lines are present :
SupportsCom=Yes
SupportsPos=Yes
The thing is, I've tried using -context to grab the nth line after the section name "[Cluster1]", but that section name is different from file to file ...
$ip = Select-String -Path "$location" -Pattern "\[Cluster1\]" -Context 0,2 |
Foreach-Object {$_.Context.PostContext}
I've tried using the Precontext to grab the Nth line before SupportsCom=Yes, but the line position of "IpAddress=" is different from file to file ...
$ip = Select-String -Path "$location" -Pattern " SupportsCom=Yes" -Context 14,0 |
Foreach-Object { $_.Line,$_.Context.PreContext[0].Trim()}
Is there a way to grab the section containing "SupportsCom=Yes" knowing that the section is delimited by a blank line above and below, then search in that section a string that contains "IpAddress=" then return the value afterthe "=" ?
Ok, since you are not allowed to use a module (perhaps later..), this should get you what you want
# change the extension in the Filter to match that of your files
$configFiles = Get-ChildItem -Path 'X:\somewhere' -Filter '*.ini' -File
$result = foreach ($file in $configFiles) {
# initialize these variables to $null
$IpAddress = $supportsCom = $supportsPos = $null
# loop through the file line by line and try regex matches on them
switch -Regex -File $file {
'^\[([^\]]+)]' {
# did we get all wanted entries from the previous cluster?
if ($IpAddress -and $supportsCom -and $supportsPos) {
if ($supportsCom -eq 'Yes' -and $supportsPos -eq 'Yes') {
# just output the IpAddress so it gets collected in variable $result
$IpAddress
}
# reset the variables to $null
$IpAddress = $supportsCom = $supportsPos = $null
}
# start a new cluster
$cluster = $matches[1]
}
'^\s+IpAddress\s*=\s*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' { $IpAddress = $matches[1]}
'^\s+SupportsCom\s*=\s*(Yes|No)' { $supportsCom = $matches[1] }
'^\s+SupportsPos\s*=\s*(Yes|No)' { $supportsPos = $matches[1]}
}
}
# show results on screen
$result
# or save as text file
$result | Set-Content -Path 'X:\somewhere\IpAddresses.txt'
Updated answer:
If you don't care about the name of the section(s), where IpAddress is found in, you can use this "one-liner" (broken into multiple lines for readability):
$ip = (Get-Content $location -Raw) -split '\[.+?\]' |
ConvertFrom-StringData |
Where-Object { $_.SupportsCom -eq 'Yes' -and $_.SupportsPos -eq 'Yes' } |
ForEach-Object IpAddress
The Get-Content line reads the input file as a single multi-line string and splits it at the section headers (e. g. [Cluster1]).
ConvertFrom-StringData converts the Key = Value lines into one hashtable per section.
For each hashtable, Where-Object checks whether it contains SupportsCom=Yes and SupportsPos=Yes
ForEach-Object IpAddress is shorthand for writing Select-Object -ExpandProperty IpAddress which gives you the actual value of IpAddress instead of an object that contains a member named IpAddress.
Note that $ip can be either a single string value or an array of strings (if there are multiple matching sections).
Original answer:
You could also write a general-purpose function that converts INI sections into objects. This enables you to use the pipeline with a simple Where-Object statement to get the data you are interested in.
Generic function to output INI sections as objects, one by one:
Function Read-IniObjects {
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline)] [String] $Path
)
process {
$section = #{} # A hashtable that stores all properties of the currently processed INI section.
# Read file line by line and match each line by the given regular expressions.
switch -File $Path -RegEx {
'^\s*\[(.+?)\]\s*$' { # [SECTION]
# Output all data of previous section
if( $section.Count ) { [PSCustomObject] $section }
# Create new section data
$section = [ordered] #{ IniSection = $matches[ 1 ] }
}
'^\s*(.+?)\s*=\s*(.+?)\s*$' { # KEY = VALUE
$key, $value = $matches[ 1..2 ]
$section.$key = $value
}
}
# Output all data of last section
if( $section.Count ) { [PSCustomObject] $section }
}
}
Usage:
$ip = Read-IniObjects 'test.ini' |
Where-Object { $_.SupportsCom -eq 'Yes' -and $_.SupportsPos -eq 'Yes' } |
ForEach-Object IpAddress
Notes:
The INI file is parsed using the switch statement, which can directly use a file as input. This is much faster than using a Get-Content loop.
As we are using -RegEx parameter, the switch statement matches each line of the file to the given regular expressions, entering the case branches only if the current line matches.
Get detailed explanation about how the RegEx's work:
match lines like [Section] -> RegEx101
match lines like Key = Value -> RegEx101
ForEach-Object IpAddress is shorthand for writing Select-Object -ExpandProperty IpAddress which gives you the actual value of IpAddress instead of an object that contains a member named IpAddress.
Note that $ip can be either a single string value or an array of strings (if there are multiple matching sections).

How to split through the whole list using PowerShell

In my CSV file I have "SharePoint Site" column and a few other columns. I'm trying to split the ID from "SharePoint Site" columns and put it to the new column call "SharePoint ID" but not sure how to do it so I'll be really appreciated If I can get any help or suggestion.
$downloadFile = Import-Csv "C:\AuditLogSearch\New folder\Modified-Audit-Log-Records.csv"
(($downloadFile -split "/") -split "_") | Select-Object -Index 5
CSV file
SharePoint Site
Include:[https://companyname-my.sharepoint.com/personal/elksn7_nam_corp_kl_com]
Include:[https://companyname-my.sharepoint.com/personal/tzksn_nam_corp_kl_com]
Include:[https://companyname.sharepoint.com/sites/msteams_c578f2/Shared%20Documents/Forms/AllItems.aspx?id=%2Fsites%2Fmsteams%5Fc578f2%2FShared%20Documents%2FBittner%2DWilfong%20%2D%20Litigation%20Hold%2FWork%20History&viewid=b3e993a1%2De0dc%2D4d33%2D8220%2D5dd778853184]
Include:[https://companyname.sharepoint.com/sites/msteams_c578f2/Shared%20Documents/Forms/AllItems.aspx?id=%2Fsites%2Fmsteams%5Fc578f2%2FShared%20Documents%2FBittner%2DWilfong%20%2D%20Litigation%20Hold%2FWork%20History&viewid=b3e993a1%2De0dc%2D4d33%2D8220%2D5dd778853184]
Include:[All]
After spliting this will show it under new Column call "SharePoint ID"
SharePoint ID
2. elksn
3. tzksn
4. msteams_c578f2
5. msteams_c578f2
6. All
Try this:
# Import csv into an array
$Sites = (Import-Csv C:\temp\Modified-Audit-Log-Records.csv).'SharePoint Site'
# Create Export variable
$Export = #()
# ForEach loop that goes through the SharePoint sites one at a time
ForEach($Site in $Sites){
# Clean up the input to leave only the hyperlink
$Site = $Site.replace('Include:[','')
$Site = $Site.replace(']','')
# Split the hyperlink at the fifth slash (Split uses binary, so 0 would be the first slash)
$SiteID = $Site.split('/')[4]
# The 'SharePoint Site' Include:[All] entry will be empty after doing the split, because it has no 4th slash.
# This If statement will detect if the $Site is 'All' and set the $SiteID as that.
if($Site -eq 'All'){
$SiteID = $Site
}
# Create variable to export Site ID
$SiteExport = #()
$SiteExport = [pscustomobject]#{
'SharePoint ID' = $SiteID
}
# Add each SiteExport to the Export array
$Export += $SiteExport
}
# Write out the export
$Export
A concise solution that appends a Sharepoint ID column to the existing columns by way of a calculated property:
Import-Csv 'C:\AuditLogSearch\New folder\Modified-Audit-Log-Records.csv' |
Select-Object *, #{
Name = 'SharePoint ID'
Expression = {
$tokens = $_.'SharePoint Site' -split '[][/]'
if ($tokens.Count -eq 3) { $tokens[1] } # matches 'Include:[All]'
else { $tokens[5] -replace '_nam_corp_kl_com$' }
}
}
Note:
To see all resulting column values, pipe the above to Format-List.
To re-export the results to a CSV file, pipe to Export-Csv
You have 3 distinct patterns you are trying to extract data from. I believe regex would be an appropriate tool.
If you are wanting the new csv to just have the single ID column.
$file = "C:\AuditLogSearch\New folder\Modified-Audit-Log-Records.csv"
$IdList = switch -Regex -File ($file){
'Include:.+(?=/(\w+?)_)(?<=personal)' {$matches.1}
'Include:(?=\[(\w+)\])' {$matches.1}
'Include:.+(?=/(\w+?)/)(?<=sites)' {$matches.1}
}
$IdList |
ConvertFrom-Csv -Header "Sharepoint ID" |
Export-Csv -Path $newfile -NoTypeInformation
If you want to add a column to your existing CSV
$file = "C:\AuditLogSearch\New folder\Modified-Audit-Log-Records.csv"
$properties = ‘*’,#{
Name = 'Sharepoint ID'
Expression = {
switch -Regex ($_.'sharepoint Site'){
'Include:.+(?=/(\w+?)_)(?<=personal)' {$matches.1}
'Include:(?=\[(\w+)\])' {$matches.1}
'Include:.+(?=/(\w+?)/)(?<=sites)' {$matches.1}
}
}
}
Import-Csv -Path $file |
Select-Object $properties |
Export-Csv -Path $newfile -NoTypeInformation
Regex details
.+ Match any amount of any character
(?=...) Positive look ahead
(...) Capture group
\w+ Match one or more word characters
? Lazy quantifier
(?<=...) Positive look behind
This would require more testing to see if it works well, but with the input we have it works, the main concept is to use System.Uri to parse the strings. From what I'm seeing, the segment you are looking for is always the third one [2] and depending on the previous segments, perform a split on _ or trim the trailing / or leave the string as is if IsAbsoluteUri is $false.
$csv = Import-Csv path/to/test.csv
$result = foreach($line in $csv)
{
$uri = [uri]($line.'SharePoint Site' -replace '^Include:\[|]$')
$id = switch($uri)
{
{-not $_.IsAbsoluteUri} {
$_
break
}
{ $_.Segments[1] -eq 'personal/' } {
$_.Segments[2].Split('_')[0]
break
}
{ $_.Segments[1] -eq 'sites/' } {
$_.Segments[2].TrimEnd('/')
}
}
[pscustomobject]#{
'SharePoint Site' = $line.'SharePoint Site'
'SharePoint ID' = $id
}
}
$result | Format-List

When creating a dictionary in PowerShell are duplicate keys all accounted for?

I'm looping through some files and pulling values into a dictionary around a ":" delimiter.
The data in the txt files looks like this:
AD ID: 9999
Ad Placement: Computers
Landing Page: www.something.com
Interests: this and that and this
Interests: also this thing and one final thing
My script for creating a dictionary looks like the following:
$files = ls "*.txt"
$dictionary = #{}
[System.Collections.Generic.List[String]]$list = #()
foreach ($f in $files) {
$in = Get-Content -Raw $f
$in.Split([Environment]::NewLine) | ForEach-Object {
$key, $value = $_.Split(':')
$dictionary[$key] = $value
}
[void]$list.Add($dictionary['Ad ID'] + ',' + $dictionary['Ad Text'] + ',' +
$dictionary['Ad Landing Page'] + ',' + $dictionary['Interests'])
}
That's the basic idea at least. I've gotten unpredictable results when I come across a file that has a key twice, as is the case in the entry in the sample data above called "Interests."
What occurs when adding dictionary items to a list from a file?
In the above example, what is the value of $dictionary['interests'] as it goes through the script?
Since the data can contain duplicate keys, you cannot use the ConvertFrom-StringData cmdlet.
To get the data in a dictionary (hashtable) manually is not that hard to do and you can decide for yourself what to do with duplicate keys: either overwrite the values so the last entry found 'wins' or not:
# this decides which duplicate value you want to store in the hashtable
$allowOverwrite = $false
$hash = #{}
# get the content of the file as string array and loop through
Get-Content -Path 'THE FULL PATH AND FILENAME OF YOUR TEXTFILE' | ForEach-Object {
if ( -not [string]::IsNullOrWhiteSpace($_)) {
# split string to get the key and the value
$key, $value = $_ -split ':', 2 | ForEach-Object { $_.Trim() }
# if a key is found that already exists in the hashtable
if ($hash.ContainsKey($key)) {
# either overwrite the value 'Last-One-Wins'
# or do nothing 'First-One-Wins'
if ($allowOverwrite) { $hash[$key] = $value }
}
else {
$hash[$key] = $value
}
}
}
$hash["interests"]
shows "this and that and this" in case of $allowOverwrite = $false
shows "also this thing and one final thing" in case of $allowOverwrite = $true

Why Isn't This Counting Correctly | PowerShell

Right now, I have a CSV file which contains 3,800+ records. This file contains a list of server names, followed by an abbreviation stating if the server is a Windows server, Linux server, etc. The file also contains comments or documentation, where each line starts with "#", stating it is a comment. What I have so far is as follows.
$file = Get-Content .\allsystems.csv
$arraysplit = #()
$arrayfinal = #()
[int]$windows = 0
foreach ($thing in $file){
if ($thing.StartsWith("#")) {
continue
}
else {
$arraysplit = $thing.Split(":")
$arrayfinal = #($arraysplit[0], $arraysplit[1])
}
}
foreach ($item in $arrayfinal){
if ($item[1] -contains 'NT'){
$windows++
}
else {
continue
}
}
$windows
The goal of this script is to count the total number of Windows servers. My issue is that the first "foreach" block works fine, but the second one results in "$Windows" being 0. I'm honestly not sure why this isn't working. Two example lines of data are as follows:
example:LNX
example2:NT
if the goal is to count the windows servers, why do you need the array?
can't you just say something like
foreach ($thing in $file)
{
if ($thing -notmatch "^#" -and $thing -match "NT") { $windows++ }
}
$arrayfinal = #($arraysplit[0], $arraysplit[1])
This replaces the array for every run.
Changing it to += gave another issue. It simply appended each individual element. I used this post's info to fix it, sort of forcing a 2d array: How to create array of arrays in powershell?.
$file = Get-Content .\allsystems.csv
$arraysplit = #()
$arrayfinal = #()
[int]$windows = 0
foreach ($thing in $file){
if ($thing.StartsWith("#")) {
continue
}
else {
$arraysplit = $thing.Split(":")
$arrayfinal += ,$arraysplit
}
}
foreach ($item in $arrayfinal){
if ($item[1] -contains 'NT'){
$windows++
}
else {
continue
}
}
$windows
1
I also changed the file around and added more instances of both NT and other random garbage. Seems it works fine.
I'd avoid making another ForEach loop for bumping count occurrences. Your $arrayfinal also rewrites everytime, so I used ArrayList.
$file = Get-Content "E:\Code\PS\myPS\2018\Jun\12\allSystems.csv"
$arrayFinal = New-Object System.Collections.ArrayList($null)
foreach ($thing in $file){
if ($thing.StartsWith("#")) {
continue
}
else {
$arraysplit = $thing -split ":"
if($arraysplit[1] -match "NT" -or $arraysplit[1] -match "Windows")
{
$arrayfinal.Add($arraysplit[1]) | Out-Null
}
}
}
Write-Host "Entries with 'NT' or 'Windows' $($arrayFinal.Count)"
I'm not sure if you want to keep 'Example', 'example2'... so I have skipped adding them to arrayfinal, assuming the goal is to count "NT" or "Windows" occurrances
The goal of this script is to count the total number of Windows servers.
I'd suggest the easy way: using cmdlets built for this.
$csv = Get-Content -Path .\file.csv |
Where-Object { -not $_.StartsWith('#') } |
ConvertFrom-Csv
#($csv.servertype).Where({ $_.Equals('NT') }).Count
# Compatibility mode:
# ($csv.servertype | Where-Object { $_.Equals('NT') }).Count
Replace servertype and 'NT' with whatever that header/value is called.

Powershell find value in arraylist

I hope you can help me out. I work with two ArrayLists:
array1 is filled with log.csv (contains header and logon-data, values of column 'pc' are unique). It's defined as ArrayList because I want to add entries.
#pc,name,date,city
#pc-x,name-1,2017-01-01,berlin
#pc-y,name-1,2017-01-02,berlin
#pc-z,name-2,2017-01-02,munich
[System.Collections.ArrayList]$array1 = Import-Csv log.csv
array2 is filled during runtime
$array2=[System.Collections.ArrayList]#()
... ForEach-Object {
$array2.Add([PSCustomObject]#{ pc=$pcname
name=$loginname
date=$logindate }) >$null }
What I want to do:
Update array1.date=array2.date where array1.pc=array2.pc
If no entry found in array1 I want to add it:
$array1.Add([PSCustomObject]#{ pc=$pcname
name=$loginname
date=$logindate
city='[unknown]' }) >$null
Finally array1 is exported again:
$array1 | Export-Csv log.csv -Encoding UTF8 -NoTypeInformation
So the question is: how can I find the entry in array1 and update it? Trying hard for days now ...
try Something like this:
$array1=import-csv "C:\temp\log.csv"
$array2=import-csv "C:\temp\log2.csv"
#modify founded and output not founded
$toadd=$array2 | %{
$current=$_
$founded=$array1 | where pc -eq $current.pc | %{$_.date=$current.date;$_}
if ($founded -eq $null)
{
$current.city='UNKNOW'
$current
}
}
#output of $array1 modified and elements to add
$array1, $toadd
Here is a sample I created that might help. Note: I use List types instead of ArrayList ones. Also, it assumes only one possible matching PC name in the data to be updated. You'll have to alter it to update the file since it merely updates the first List variable. Let me know how it goes.
[PSCustomObject]
{
[string] $pc,
[string] $name,
[string] $date,
[string] $city
}
[System.Collections.Generic.List[PSCustomObject]] $list1 = Import-Csv "C:\SOSamples\log.csv";
[System.Collections.Generic.List[PSCustomObject]] $list2 = Import-Csv "C:\SOSamples\log2.csv";
[PSCustomObject] $record = $null;
[PSCustomObject] $match = $null;
foreach($record in $list2)
{
# NOTE: This only retrieves the FIRST MATCHING item using a CASE-INSENSITIVE comparison
$match = $list1 | Where { $_.pc.ToLower() -eq $record.pc.ToLower() } | Select -First 1;
if($match -eq $null)
{
Write-Host "Not Found!";
$list1.Add($record);
}
else
{
Write-Host "Found!";
$match.date = $record.date;
}
}
Write-Host "--------------------------------------------------------------------"
foreach($record in $list1)
{
Write-Host $record
}