Powershell - ODBC MSAccess Commit transaction refresh data for Select Query after Insert - powershell

I'm connecting with ODBC in powershell to an .accdb Access Database. After inserting new data into a table (insert into via ExecuteNonQuery), I'm unable to pull the data with a select command for futher processing (create a PDF table). The resulting object of the query is empty (0 rows), though "Select ##IDENTITY" returns the most recent index identifier. The Workflow is something like this:
Data entered into a forms window -> new record is created -> data is pulled off and processed into a report -> report is sent by mail -> record is flagged as sent
I tried to use the SQL transaction mechanism, but even after the commit is done, the data is not immediately retrievable with a select-query. After inserting another record, the previously inserted one can be pulled with a select query, but not the new most recent one.
I have not found any working solution to this behavior. I've heard about this, when using Access forms, where the data will be available when the form has been moved to the next record. Any ideas how I can solve this?
Below are my getter and setter functions. In general they work as intended but using Set-Data with an insert first and firing Get-Data with a select afterwards does not provide any records.
Function Get-Data($SQLCommand){
try{
$Connection = New-Object System.Data.Odbc.OdbcConnection($DSN)
$Connection.Open()
$AccdbCommand = New-Object System.Data.Odbc.OdbcCommand($SQLCommand, $Connection)
$AccdbAdapter = New-Object System.Data.Odbc.OdbcDataAdapter($AccdbCommand)
$AccdbDataSet = New-Object System.Data.DataSet
$AccdbAdapter.Fill($AccdbDataSet)
$Connection.Close()
return $AccdbDataSet
}
catch
{
[System.Windows.Forms.MessageBox]::Show($_.Exception.Message + "`r`n" + $_.Exception.InnerException, "Fehler",'OK','Error')
}
}
Function Set-Data($SQLCommand){
try
{
$Connection = New-Object System.Data.Odbc.OdbcConnection($DSN)
$Connection.Open()
$Transaction = $Connection.BeginTransaction()
$AccdbCommand = New-Object System.Data.Odbc.OdbcCommand($SQLCommand, $Connection, $Transaction)
$AccdbCommand.ExecuteNonQuery()
$transaction.Commit()
$AccdbCommand = New-Object System.Data.Odbc.OdbcCommand("Select ##IDENTITY", $Connection)
$result = $AccdbCommand.ExecuteScalar()
$Connection.Close()
}
catch
{
[System.Windows.Forms.MessageBox]::Show($_.Exception.Message + "`r`n" + $_.Exception.InnerException, "Fehler", 'OK', 'Error')
}
}

I've resolved this Problem. The commit itself is working as intended. The missing data record in the output process was a result of an error in looping the rows in the dataset with a for Statement.

Related

Set ContextMenu items from a string list

Basically I'm trying to create a ContextMenu that shows a list of mounted network drive, so the user can click on one and access it.
The problem is : the list is not fix, but depend on a string list extracted from a DataGrid.
In my mind, I could just create the menu items by looping on that string list.
Here is the code sample :
$Main_Tool_Icon = New-Object System.Windows.Forms
[...]
# Add menu entries
$contextmenu = New-Object System.Windows.Forms.ContextMenu
$Main_Tool_Icon.ContextMenu = $contextmenu
foreach ($nameTag in $dataGrid[$DATAGRID_COLUMN_NAME]){
$menuEntry = New-Object System.Windows.Forms.MenuItem
$menuEntry.Text = $nameTag
$Main_Tool_Icon.ContextMenu.MenuItems.AddRange($menuEntry)
}
# Add separator
$Main_Tool_Icon.ContextMenu.MenuItems.Add('-')
# Last one to show main form
$displayMainform = New-Object System.Windows.Forms.MenuItem
$displayMainform.Text = "Edit"
$Main_Tool_Icon.ContextMenu.MenuItems.Add($displayMainform)
It appears that's wrong and only shows the separator and last "Edit" line.
Is there a way to populate a contextual menu with a list ?
Or I should look for a better way to do the job ?
EDIT #Mathias R. Jessen
You're right about AddRange(), it's just a mistake.
I can't show everything since it's quite big. I'll try to synthesize an answer properly.
The data grid is created/populated by my script and is just a table of strings from a csv file.
$DATAGRID_COLUMN_NAME = 'NAME' it's one of constants defined for my datagrid headers.
Here is the setup
# set datagrid
$dataGrid = New-Object System.Windows.Forms.DataGridView
[...]
$dataGrid.Columns.Add($DATAGRID_COLUMN_NAME,'Name')
$dataGrid.Columns[0].DataPropertyName = $dataGrid.Columns[0].Name
Then some databinding
# initiate the data table
$table = New-Object System.Data.DataTable
[...]loop on csv file[...]
# bind data table to data grid
$dataGrid.DataSource = $table
EDIT #SantiagoSquarzon
You were right for pointing out $dataGrid[$DATAGRID_COLUMN_NAME]. The syntax was bad and returned something null. Instead, I looped over the $dataGrid.Rows like so:
foreach ($row in $dataGrid.Rows){
$nameTag = $row.Cells[$DATAGRID_COLUMN_NAME].Value
if ($nameTag -ne $null){
$menuEntry = New-Object System.Windows.Forms.MenuItem
$menuEntry.Text = $nameTag
$Main_Tool_Icon.ContextMenu.MenuItems.AddRange($menuEntry)
}
}
Now I just have to figure out how to invoke there click event.

ODBC Data Reader HasRows=True but No Data

I'm connecting to an Access database through ODBC using Powershell, reading data from Access to convert it to another database. I have one query that is causing me problems because although HasRows returns True, when I attempt to loop through the reader, it acts as if it is at the end of the reader. Here is the code I'm using
$FMconn = new-object System.Data.Odbc.OdbcConnection
$FMconn.ConnectionString = "DSN=AccessFileMaker"
$FMconn.Open()
$FMCmd = $FMconn.CreateCommand()
$FMCmd.CommandTimeout = 0
$sql = "
SELECT
fd__folder.id as folderid
,fd__folder.folderletter
,wl__wallet.id as walletid
,wl__wallet.walletnumber
FROM
fd__folder
INNER JOIN wl__wallet
ON fd__folder.id_wallet = wl__wallet.id
WHERE
fd__folder.folderLetter > ''
"
$FMCmd.CommandText = $sql
$Reader = $FMCmd.ExecuteReader()
if ($Reader.HasRows) {
while ($Reader.Read())
{
$folderID = $Reader["folderid"].ToString()
$folderletter = $Reader["folderletter"].ToString().TrimEnd()
$walletID = $Reader["walletid"].ToString()
$walletNum = "Wallet " + $Reader["walletnumber"].ToString().TrimEnd()
(other code here to insert data into the other DB but irrelevant to the problem)
}
$Reader.Close()
}
As I step through the code I get to the while($Reader.Read()) and it immediately steps to $Reader.Close()
The query runs find in Access as is and returns 56115 records. I've used this same process to extract other records from Access (although none of the queries involved a join). So why can't I loop through the records of this query if the Reader has rows? Any help debugging this would be greatly appreciated.

How to replace/update the Value of an Attribute in LDAP Directory using PowerShell?

The entries in our companys Non-AD LDAP Server look like this:
uid = e145871
sn = Smith
givenName = John
department = Research & Development
department = Human Resource
And so on...
I've developed a PowerShell script to add specific attributes and values which is working just fine. Now I need to replace specific values but the issue is the identical attribute name. (In this case it's "department")
My goal is to replace "Research & Development" with "Something Else". If I run the following script it gets replaced but Human Resource is deleted as well. Is it possible to replace only one value without touching/deleting the other?
$r = New-Object -TypeName System.DirectoryServices.Protocols.ModifyRequest
$r.DistinguishedName = "uid=e145871,ou=identities,ou=users,o=items,dc=company,dc=domain,dc=com"
$DirectoryRequest_value = New-Object "System.DirectoryServices.Protocols.DirectoryAttributeModification"
$DirectoryRequest_value.Name = "department"
$DirectoryRequest_value.Contains("Research & Development")
$DirectoryRequest_value.Operation = [System.DirectoryServices.Protocols.DirectoryAttributeOperation]::Replace
$DirectoryRequest_value.Add("SomethingElse")
$r.Modifications.Add($DirectoryRequest_value)
$result = $connection.SendRequest($r)
Thanks!
The LDAP Replace operation replaces (or overwrites) the entire value of the attribute, including any existing values that might exist as part of a multi-valued attribute.
From RFC4511 ยง4.6 - "Modify Operation":
- operation: Used to specify the type of modification being
performed. Each operation type acts on the following
modification. The values of this field have the following
semantics, respectively:
[...]
replace: replace all existing values of the modification
attribute with the new values listed, creating the attribute
if it did not already exist. A replace with no value will
delete the entire attribute if it exists, and it is ignored
if the attribute does not exist.
Instead, add two separate modifications to the request - one to add "SomethingElse" and one to remove "Research & Development":
$targetObject = 'uid=e145871,ou=identities,ou=users,o=items,dc=company,dc=domain,dc=com'
$attributeName = 'department'
$oldValue = 'Research & Development'
$newValue = 'SomethingElse'
$request = [System.DirectoryServices.Protocols.ModifyRequest]::new()
$request.DistinguishedName = $targetObject
# This modification will add the new value "SomethingElse"
$addNewDepartment = #{
Name = $attributeName
Operation = 'Add'
} -as [System.DirectoryServices.Protocols.DirectoryAttributeModification]
$addNewDepartment.Add($newValue) |Out-Null
$request.Modifications.Add($addNewDepartment) |Out-Null
# This modification will remove the old value "Research & Development"
$removeOldDepartment = #{
Name = $attributeName
Operation = 'Delete'
} -as [System.DirectoryServices.Protocols.DirectoryAttributeModification]
$removeOldDepartment.Add($oldValue) |Out-Null
$request.Modifications.Add($removeOldDepartment) |Out-Null
$result = $connection.SendRequest($request)

Powershell Multiple variables in foreach loop getting AWS-EC2 instance info

I need to get instanceId and PrivateIpAddress from all my EC2 instances in my environment and insert into table. I have this but doesn't seem to work. I seem to get everything not just the IP and ID.
$instancestate = (get-ec2instance).RunningInstance
foreach ($instances in $instancestate)
{
$query = "INSERT INTO prodinstances (Idinstance, IPAddress)
VALUES ('$instances.InstanceId','$instances.PrivateIpAddress')"
$Rows = Execute-MySQLNonQuery $conn $query
}
If I change the code
$instancestate = (get-ec2instance).RunningInstance.InstanceId
I get the ID and can insert it in the database. I can also change it to
$instancestate = (get-ec2instance).RunningInstance.PrivateIpAddress
and get the IPAddress and insert that into the database, but when i combine them I get all the info for the EC2 instances which does have .instanceId and .PrivateIpAddress in the list when I hover over the variable $instances. Any Idea how to get both those parameters. My code seems correct but alas it is not...
"VALUES ('$instances.InstanceId'"
is the same as
"VALUES ('" + $instances + ".InstanceId'"
Now it doesn't seem correct. You need $() around it, inside the string:
"VALUES ('$($instances.InstanceId)'"
Fixed works like a charm...
$instancestate = (get-ec2instance).RunningInstance
foreach ($instances in $instancestate)
{
$query = "insert into prodinstances (idinstance,IPAddress) VALUES ('$($instances.InstanceId)', '$($instances.PrivateIpAddress)')
ON duplicate key update idinstance='$($instances.InstanceId)',IPAddress='$($instances.PrivateIpAddr ess)'"
$Rows = Execute-MySQLNonQuery $conn $query
}

Can I tell if an ado.net DbCommand is a query or not (before executing it)

I am trying to write a Powershell script to run a general SQL command against a database. The idea is that Run-SQL "select ..." will run the SQL text against the currently open database. If the SQL statement is a query, it should return a DataTable. If it is a non-query (DDL or DML) it should return nothing ($null).
In order to do this, I need to know which method (ExecuteReader or ExecuteNonQuery) to execute against the command. Is there a way to determine this? (I'm happy to prepare the command if that helps).
As an alternative, I can add a -query argument to be supplied by the user, which distinguishes the two cases, but as a potential user, I'd find this annoying (as, in my view, I have already said whether it's a query by the SQL I used, why say again?)
My key use is for Oracle databases, so an Oracle-specific answer is OK with me, although I'd prefer something generic.
I think you could just use ExecuteReader whether it's a query or not. It may be overkill but in some quick experiments with doing an UPDATE ($reader returns nothing) and a COUNT ($reader[0] outputs scalar result) - it just seems to work.
$connstr = "server=.\SQLEXPRESS;Database=AdventureWorks;" +
"Integrated Security=true;Persist Security Info=False"
$conn = new-object System.Data.SqlClient.SqlConnection $connstr
#$query = "UPDATE Production.Product SET Name = 'ACME' WHERE Name = 'Blade'"
$query = "SELECT Count(*) FROM Production.Product"
$cmd = new-object System.Data.SqlClient.SqlCommand $query,$conn
$conn.Open()
try
{
$reader = $cmd.ExecuteReader()
while ($reader.Read())
{
$reader[0]
}
}
finally
{
$conn.Dispose()
}
As an alternative to what Keith said you could try
$sql = 'Select count(1) From SomeTable;'
$sql = $sql.TrimStart(' ')
if ($sql -match "^select") { Write-Host 'ExecuteReader' }
else { Write-Host 'ExecuteNonQuery'}
The trim is there in case the SQL command has a leading space since $sql = ' select ' won't match "^select"