DbConnectionStringBuilder does not parse when used in PowerShell - powershell

I am trying to use capabilities of DbConnectionStringBuilder for parsing connection-string-like user input. This works just fine in C#:
using System;
using System.Data.Common;
class Program
{
static void Main(string[] args)
{
var sb = new DbConnectionStringBuilder();
sb.ConnectionString = "server = 'smtp.gmail.com'; port = 587; user = you#gmail.com";
Console.WriteLine(sb["server"]);
Console.WriteLine(sb["port"]);
Console.WriteLine(sb["user"]);
}
}
Output:
smtp.gmail.com
587
you#gmail.com
But it does not work in PowerShell. Namely, this literally translated code in PowerShell
$sb = New-Object System.Data.Common.DbConnectionStringBuilder
$sb.ConnectionString = "server = 'smtp.gmail.com'; port = 587; user = you#gmail.com"
$sb["server"]
$sb["port"]
$sb["user"]
produces no output at all.
Any ideas why? How to make DbConnectionStringBuilder to work as a parser in PowerShell?

System.Data.Common.DbConnectionStringBuilder implements IDictionary. Powershell has a shorthand for dictionaries using . that allows retrieval and assignment of key/value pairs as if the key was a property:
$dict = #{}
$dict["key1"] = 'value1'
$dict.key2 = 'value2'
You can see that it is storing the entire connection string as a key/value pair instead of on the ConnectionString property this way:
$sb = New-Object System.Data.Common.DbConnectionStringBuilder
$sb.ConnectionString = "server = 'smtp.gmail.com'; port = 587; user = you#gmail.com"
$sb #formatted table of key/value pairs
The easiest way to get around this is to call the set_/get_ methods generated for properties:
$sb = New-Object System.Data.Common.DbConnectionStringBuilder
$sb.set_ConnectionString("server = 'smtp.gmail.com'; port = 587; user = you#gmail.com")
$sb["server"]
$sb["port"]
$sb["user"]

It is probably a bug (a gotcha anyway) and I will submit it soon. It looks like PowerShell does not call setters on properties of classes that implement IDictionary (as DbConnectionStringBuilder does, and it is the setter of ConnectionString that parses the string).
Here are two demos (the original and workaround):
# test 1 - does not work, presumably PowerShell invokes $sb["ConnectionString"] = .. instead of the setter
$sb = New-Object System.Data.Common.DbConnectionStringBuilder
$sb.ConnectionString = "server = 'smtp.gmail.com'; port = 587; user = you#gmail.com"
# the original string
$sb.ConnectionString
# nothing at all
$sb["server"]
$sb["port"]
$sb["user"]
# test 2 - works fine, we make PowerShell to invoke the ConnectionString property setter in this way
$sb = New-Object System.Data.Common.DbConnectionStringBuilder
$sb.PSObject.Properties['ConnectionString'].Value = "server = 'smtp.gmail.com'; port = 587; user = you#gmail.com"
# parsed and re-formatted string
$sb.ConnectionString
# parsed data
$sb["server"]
$sb["port"]
$sb["user"]
Output:
server = 'smtp.gmail.com'; port = 587; user = you#gmail.com
server=smtp.gmail.com;port=587;user=you#gmail.com
smtp.gmail.com
587
you#gmail.com
As far as the workaround is found we get for free a pretty powerful parser for connection-string-like data. Here is the demo that shows parsing of quite convoluted input:
# get the parser
$sb = New-Object System.Data.Common.DbConnectionStringBuilder
# input the string to parse using this workaround way
$sb.PSObject.Properties['ConnectionString'].Value = #'
Apostrophes = "Some 'value'";
Quotations = 'Some "value"';
Semicolons = '1; 2; 3';
Multiline = Line1
Line2
Line3;
Name with spaces = Some value;
Name with == sign = Some value;
'#
# get the parsed results
# the string: not the original but parsed and re-formatted
$sb.ConnectionString
# the data: parsed key/values
$sb | Format-Table -AutoSize -Wrap
Output:
apostrophes="Some 'value'";quotations='Some "value"';semicolons="1; 2; 3";multiline="Line1
Line2
Line3";name with spaces="Some value";name with == sign="Some value"
Key Value
--- -----
apostrophes Some 'value'
quotations Some "value"
semicolons 1; 2; 3
multiline Line1
Line2
Line3
name with spaces Some value
name with = sign Some value

$sb = New-Object System.Data.Common.DbConnectionStringBuilder
$sb.Add("server","smtp.gmail.com")
$sb.Add("port",587)
$sb.Add("user","you#gmail.com")
$sb["server"]
$sb["port"]
$sb["user"]

Related

Is it possible to pull multiple SNMP values from try..catch statements?

Is there a way to pull multiple SNMP values and update them as new values in PowerShell?
I have two different OIDs that I'm pulling from my UPS and I want those integer values saved to a database table.
This is my code
$UPS_Temp_oid = '1.3.6.1.4.1.318.1.1.10.2.3.2.1.4.1'
$Battery_Load_oid = '.1.3.6.1.4.1.318.1.1.1.4.2.3.0'
$sql = "SELECT temp, batteryload, upsid, ups_ip FROM ups WHERE ups_ip IS NOT NULL"
$cmd = New-Object System.Data.SqlClient.SqlCommand($sql, $conn)
$rows = $cmd.ExecuteReader()
while ($rows.Read()) {
$ups_id = $rows["upsid"]
$ups_ip = $rows["ups_ip"].trim()
$ups_temp = $rows["temp"]
$battery_load = $rows["batteryload"]
Write-Output $ups_id, $ups_ip, $ups_temp, $battery_load
# Ping UPS
$ping = New-Object System.Net.NetworkInformation.Ping
$ping_reply = $ping.Send($ups_mgmt_ip) | select status
# If success go call func SNMP
if ($ping_reply.status -eq "Success") {
try {
$frm_snmp = Invoke-SNMPget $ups_ip $UPS_Temp_oid "community"
} catch {
Write-Host "$ups_mgmt_ip SNMP Get error: $_"
return null
}
would I have to create another try..catch for the battery_load_oid or would I just simply do something like that?
Invoke-SNMPget ups_ip $ups_temp_oid, $nattery_load_oid "community"

Null valued expression

foreach ($OldAccount in $forwarderarray) {
$OldAccount.ForwardingAddress = $OldAccount.ForwardingAddress.substring(0,1).toupper()+$OldAccount.ForwardingAddress.substring(1).tolower()
$Separatorforwarderaddress = $OldAccount.UserPrincipalName.IndexOf("#") # This section truncates the SMTP address to the firstname.surname to remove the #
$LeftPartforwarderaddress = $OldAccount.UserPrincipalName.Substring(0, $Separatorforwarderaddress)
$RightPartforwarderaddress = $OldAccount.UserPrincipalName.Substring($Separatorforwarderaddress + 2)
$SeparatorOwnerAddress = $OldAccount.ForwardingAddress.IndexOf(".") # This section truncates the SMTP address to the firstname.surname to remove the #
$LeftPartOwnerAddress = $OldAccount.ForwardingAddress.Substring(0, $SeparatorOwnerAddress)$RightPartOwnerAddress =
$OldAccount.ForwardingAddress.Substring($SeparatorOwnerAddress + 2)
What is strange is the write host displays the value expected
But when I try to SMTP
Send-MailMessage -to $OldAccount.ForwardingAddress
I ge:
You cannot call a method on a null-valued expression
Can someone please help? What I am trying to achieve is to take out the email address for display purposes and to make the name start with a capital letter.
So why does this display the correct information
Write-Host "Sending message, to:"$OldAccount.ForwardingAddress

Get all IP Addresses assigned to an EC2 Instance

The function below as well as others that I have in the script to collect a complete inventory of everything we have in AWS, runs without any problems.
However, I am missing all of the IP addresses that are assigned to the instance after the first one when the instance has more than one interface.
How can I make sure to get all the ip addresses of every instance in the function below before writing the details into the excel worksheet?
function Create-EC2InstanceWorksheet {
#Creating EC2 Instances Worksheet
# Add Excel worksheet
$workbook.Worksheets.Add()
# We need to create a sheet for the Instances
$InstancesWorksheet = $workbook.Worksheets.Item(1)
$InstancesWorksheet.Name = 'Instances'
# Headers for the Instance worksheet
$InstancesWorksheet.Cells.Item(1,1) = 'Region'
$InstancesWorksheet.Cells.Item(1,2) = 'Instance Name'
$InstancesWorksheet.Cells.Item(1,3) = 'Image ID'
$InstancesWorksheet.Cells.Item(1,4) = 'Instance ID'
$InstancesWorksheet.Cells.Item(1,5) = 'PEM File'
$InstancesWorksheet.Cells.Item(1,6) = 'Instance Type'
$InstancesWorksheet.Cells.Item(1,7) = 'Private IP'
$InstancesWorksheet.Cells.Item(1,8) = 'Public IP'
$InstancesWorksheet.Cells.Item(1,9) = 'VPC ID'
$InstancesWorksheet.Cells.Item(1,10) = 'Subnet ID'
$InstancesWorksheet.Cells.Item(1,11) = 'State'
$InstancesWorksheet.Cells.Item(1,12) = 'Security Group Id'
$InstancesWorksheet.Cells.Item(1,13) = 'Source/Dest Check'
# Excel Cell Counter
$row_counter = 3
$column_counter = 1
# Get the Ec2 instances for each region
foreach($AWS_Locations_Iterator in $AWS_Locations){
$EC2Instances = Get-EC2Instance -Region $AWS_Locations_Iterator
# Iterating over each instance
foreach($EC2Instances_Iterator.Instances.NetworkInterfaces.PrivateIpAddresses.PrivateIpAddress in $EC2Instances){
foreach($EC2Instances_Iterator.Instances.NetworkInterfaces.Pr ...
+ ~
Missing 'in' after variable in foreach loop.
Remove the code above starting at foreach and used the suggestion provided by #AnthonyNeace. Replaced with the foreach below which does provide the additional ip addresses.
foreach($instance in $EC2Instances.Instances){
foreach($networkInterface in $instance.NetworkInterfaces){
"$($instance.InstanceID): $($networkInterface.PrivateIpAddresses.PrivateIpAddress)";
# Ignore if a region does not have any instances
if($EC2Instances_Iterator.count -eq $null) {
continue
}
# Populating the cells
$InstancesWorksheet.Cells.Item($row_counter,$column_counter++) = $AWS_Locations_Iterator
$InstancesWorksheet.Cells.Item($row_counter,$column_counter++) = $EC2Instances_Iterator.Instances.Tags.value
$InstancesWorksheet.Cells.Item($row_counter,$column_counter++) = $EC2Instances_Iterator.Instances.imageid
$InstancesWorksheet.Cells.Item($row_counter,$column_counter++) = $EC2Instances_Iterator.Instances.Instanceid
$InstancesWorksheet.Cells.Item($row_counter,$column_counter++) = $EC2Instances_Iterator.Instances.keyname.tostring()
$InstancesWorksheet.Cells.Item($row_counter,$column_counter++) = $EC2Instances_Iterator.Instances.Instancetype.Value
$InstancesWorksheet.Cells.Item($row_counter,$column_counter++) = $EC2Instances_Iterator.Instances.PrivateIpAddress
$InstancesWorksheet.Cells.Item($row_counter,$column_counter++) = $EC2Instances_Iterator.Instances.PublicIpAddress
$InstancesWorksheet.Cells.Item($row_counter,$column_counter++) = $EC2Instances_Iterator.Instances.VpcId
$InstancesWorksheet.Cells.Item($row_counter,$column_counter++) = $EC2Instances_Iterator.Instances.SubnetId
$InstancesWorksheet.Cells.Item($row_counter,$column_counter++) = $EC2Instances_Iterator.Instances.state.name.value
$InstancesWorksheet.Cells.Item($row_counter,$column_counter++) = $EC2Instances_Iterator.Instances.securitygroups.GroupId
$InstancesWorksheet.Cells.Item($row_counter,$column_counter++) = $EC2Instances_Iterator.Instances.SourceDestCheck
# Seting the row and column counter for next EC2 instance entry
$row_counter = $row_counter + 1
$column_counter = 1
}
# Iterating to the next region
$row_counter = $row_counter + 1
}
}
The network interface is included with each EC2 Instance in the Get-EC2Instance response, so taking the private ip addresses as an example... you could access the private IP addresses by simply iterating over each private ip address exposed on each network interface. Same for the IPv6 addresses.
Object Model
Amazon.EC2.Model.Instance
System.Collections.Generic.List<Amazon.EC2.Model.InstanceNetworkInterface>
System.Collections.Generic.List<Amazon.EC2.Model.InstancePrivateIpAddress>
PrivateIpAddress
System.Collections.Generic.List<Amazon.EC2.Model.InstanceIpv6Address>
Ipv6Address
IPv4 Example
$EC2Instances_Iterator.Instances.NetworkInterfaces.PrivateIpAddresses.PrivateIpAddress
IPv6 Example
$EC2Instances_Iterator.Instances.NetworkInterfaces.Ipv6Addresses.Ipv6Address
Example: Write addresses string using foreach loop
This example builds the addresses across each network interface into a comma-delimited string within the scope of the instances loop. So a string is created for each instance.
The powershell subexpression operator $() is used to resolve complex properties in the string.
$EC2Instances = Get-EC2Instance
foreach($instance in $EC2Instances.Instances){
$addresses = "";
foreach($networkInterface in $instance.NetworkInterfaces){
$addresses = $addresses, $networkInterface.PrivateIpAddresses.PrivateIpAddress -join ","
}
"$($instance.InstanceID): $($addresses.Trim(','))"
}
Further Reading
AWS Documentation - Get-EC2Instance

Is it possible to update a row in MS Access from PowerShell?

I have the following PowerShell code which adds a new line of data into a MS Access database (based on user input) and works perfectly.
if ($NewAccounts ="Y") {
$cursor = 3
$lock = 3
$Ado = New-Object -ComObject ADODB.Connection
$recordset = New-Object -ComObject ADODB.Recordset
$Ado.Open("Provider = Microsoft.ACE.OLEDB.12.0;Data Source=$Source")
$query = "Select * from [Sheet1]"
$recordset.Open($query, $ado, $cursor, $lock)
$recordset.AddNew()
$recordset.Fields.Item("Account") = $AccName
$recordset.Fields.Item("Serial") = $CGBSerial
$recordset.Fields.Item("SAExpiry") = $SAEDate.ToString("dd/MM/yyyy")
$recordset.Fields.Item("SAValidatedPerson") = $SAPerson
$recordset.Fields.Item("DataCollection") = $DCRun
$recordset.Fields.Item("DataCollectionDate") = $DCRunDate
$recordset.Fields.Item("DataCollectionPerson") = $DCPerson
$recordset.Fields.Item("Version") = $Version
$recordset.Fields.Item("VersionDateValidated") = Get-Date -Format d
$recordset.Fields.Item("VersionValidatedPerson") = $logontrim
$recordset.Update()
$recordset.Close()
$ado.Close()
}
However, I cannot seem to update a row in the database that already exists. Is it possible to update a row, rather than creating an entirely new row?
$recordset.AddNew() appends a new empty record to the recordset. To update an existing record you need to navigate to the record you want to modify first, and then change the values of that record.
$recordset.Open($query, $ado, $cursor, $lock)
while ($recordset.Fields.Item('Account').Value -ne $AccName) {
$recordset.MoveNext()
}
$recordset.Fields.Item('Serial') = $CGBSerial
...
$recordset.Update()
$recordset.Close()
However, you can't use MoveNext() with a static cursor, so you need to change the cursor type to adOpenForwardOnly ($cursor = 0).
Alternatively you could use a prepared statement:
$cn = New-Object -ComObject 'ADODB.Connection'
$cn.ConnectionString = "..."
$cn.Open()
$cmd = New-Object -ComObject 'ADODB.Command'
$cmd.CommandText = 'UPDATE [Sheet1] SET Serial=?, SAExpiry=?, ... WHERE Account=?'
$cmd.Parameters.Append($cmd.CreateParameter('#p1', 200, 1, 50, $CGBSerial))
$cmd.Parameters.Append($cmd.CreateParameter('#p2', 7, 1, $null, $SAEDate))
...
$cmd.Execute()
$cn.Close()

How To Execute MS Access Query with OLEDB.12

I am looking for the syntax for executing MS Access named query using Microsoft.ACE.OLEDB.12.0 command object.
I see lots of examples using tables but non for queries yet. Swapping out the table name for the query name seems not to work. i.e. select * from 'myquery'
Here is my code snippet:
$OleDbConn = New-Object "System.Data.OleDb.OleDbConnection";
$OleDbCmd = New-Object "System.Data.OleDb.OleDbCommand";
$OleDbAdapter = New-Object "System.Data.OleDb.OleDbDataAdapter";
$DataTable = New-Object "System.Data.DataTable";
$OleDbConn.Open();
$OleDbCmd.Connection = $OleDbConn;
$OleDbCmd.CommandText = "'myQuery'"; # name query in MS Access db
$OleDbCmd.CommandType = [System.Data.CommandType]::StoredProcedure;
$OleDbAdapter.SelectCommand = $OleDbCmd;
$RowsReturned = $OleDbAdapter.Fill($DataTable);
Write-Host $RowsReturned;
Error: Exception calling "Fill" with "1" argument(s): "The Microsoft Access database engine cannot find the input table or query ''Lab Manual''. Make sure it exists and that its name is spelled correctly."
The trick was to append the command 'Execute' before the query name and use square brackets around the query name.
$OleDbConn = New-Object "System.Data.OleDb.OleDbConnection";
$OleDbCmd = New-Object "System.Data.OleDb.OleDbCommand";
$OleDbAdapter = New-Object "System.Data.OleDb.OleDbDataAdapter";
$DataTable = New-Object "System.Data.DataTable";
$OleDbConn.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=c:\temp\labmanual.mdb;";
$OleDbCmd.Connection = $OleDbConn;
$OleDbCmd.CommandText = "Execute [myQuery]";
$OleDbAdapter.SelectCommand = $OleDbCmd;
$OleDbConn.Open();
$RowsReturned = $OleDbAdapter.Fill($DataTable);
Write-Host $RowsReturned;
$OleDbConn.Close();