Using Dapper in Powershell - powershell

I am trying to get Dapper to work in Powershell. I was not very successfull searching the internet for code samples, so I tried to roll my own ... and failed.
Here is what I did:
I am in C:\test and have an open powershell (in Windows Terminal that is). The following files are in C:\test\database:
Dapper.dll
Dapper.xml
database.db
database.sql
SQLite.Interop.dll
System.Data.SQLite.dll
System.Data.SQLite.dll.config
The SQLite DLLs come from https://system.data.sqlite.org/downloads/1.0.117.0/sqlite-netFx46-binary-x64-2015-1.0.117.0.zip and were copied directly from the extracted archive to C:\test\database
The Dapper.dll and Dapper.xml were copied from a current Visual Studio 2017 C# Projects packages directory.
database.db contains a valid SQLite database that was created with the database.sql:
create table keypad (
id INTEGER PRIMARY KEY AUTOINCREMENT,
col1 VARCHAR(20),
col2 VARCHAR(20),
col3 VARCHAR(20)
);
insert into keypad values ( 1, "7", "8", "9" );
insert into keypad values ( 2, "4", "5", "6" );
insert into keypad values ( 3, "1", "2", "3" );
insert into keypad values ( 4, "0", "0", "," );
database.db can be queried with the following PS commands:
Add-Type -Path "C:\test\database\System.Data.SQLite.dll"
$con = [System.Data.SQLite.SQLiteConnection]::new("Data Source=C:\test\database\database.db")
$con.Open()
$cmd = [System.Data.SQLite.SQLiteCommand]::new("select * from keypad", $con)
$rdr = $cmd.ExecuteReader()
$rdr.HasRows
$rdr.FieldCount
$f=""; $fc = 0; 0..($rdr.FieldCount-1) | foreach { $f = "$f {$fc,5}"; $fc++ }
$f -f (0..($rdr.FieldCount-1) | foreach { $rdr.GetName($_) }); while ( $rdr.Read() ) { $f -f (0..($rdr.FieldCount-1) | foreach { $rdr.GetValue($_) }) }
Result is as expected:
id col1 col2 col3
1 7 8 9
2 4 5 6
3 1 2 3
4 0 0 ,
I would like to use Dapper for querying this table. I tried:
Add-Type -Path "C:\test\database\System.Data.SQLite.dll"
Add-Type -Path "C:\test\database\Dapper.dll"
$con = [System.Data.SQLite.SQLiteConnection]::new("Data Source=C:\test\database\database.db")
$con.Open()
$sql = "select * from keypad where id = 1"
$res = $con.Query[dynamic]( $sql ).FirstOrDefault();
Everything above the last command does execute flawlessly, but the last command yields an ugly error message:
At line:1 char:19
+ $res = $con.Query[dynamic]( $sql ).FirstOrDefault();
+ ~
Array index expression is missing or not valid.
At line:1 char:19
+ $res = $con.Query[dynamic]( $sql ).FirstOrDefault();
+ ~~~~~~~~
Unexpected token 'dynamic]' in expression or statement.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : MissingArrayIndexExpression
I am a bit puzzled how Dapper does do it's magic, since $con is essentially a System.Data.SQLite.SQLiteConnection (at least Get-Member thinks of it this way), which does not know of a method by the name of Query.
How do I get Dapper to work in Powershell?

Related

Take value from SQL server and delete relevant folders

Im new to powershell and would like to
-delete all rows in a sql server DB that have a date older than 10 years
-for every row that is deleted also delete a folder or a hard disk
So for example if I run the query
DELETE FROM [RMS].[dbo].[requests] where date_logged < DATEADD(year, -10, GetDate())
I then thought I could get the lowest request_id and just delete any folders under that number.
So for example if I delete 10 rows with my delete query and then do a select
It would say that the lowest request_id is 11.
I've started below but I'm not sure how to capture that the oldest request_id is?
The SQL would be this ...
SELECT TOP 1 request_id FROM [RMS].[dbo].[requests] order by request_id asc
And also how I would delete any folder "less" than that value.
So if request_id = 11 then I'd need to delete
C:\temp\1
C:\temp\2
C:\temp\3
...
C:\temp\10
Thanks
P
$connectionString = "Data Source=server;Initial Catalog=RMS;Integrated Security=SSPI";
$connection = New-Object System.Data.SqlClient.SqlConnection($connectionString);
$commandR = New-Object System.Data.SqlClient.SqlCommand("DELETE FROM dbo.requests WHERE request_id= 1", $connection);
$commandCount = New-Object System.Data.SqlClient.SqlCommand("select count(*) from requests", $connection);
$connection.Open();
$rowsDeletedR = $commandR.ExecuteNonQuery();
Write-Host "$rowsDeletedR rows deleted";
$rowsCountR = $commandCount.ExecuteScalar();
Write-Host "$rowsCountR rows in requests table";
$connection.Close();
Your task is broad. I intentionally splitted it into smaller pieces. Take a look at this demo and comments.
Since Invoke-SqlCmd is considered harmful (SQL Injection), I use my own function to invoke SQL
function Invoke-Sql(
$ConnectionString,
$Query,
$Parameters
) {
$conn = New-Object System.Data.SqlClient.SqlConnection -ArgumentList $ConnectionString
$cmd = New-Object System.Data.SqlClient.SqlCommand -ArgumentList $Query,$conn
$conn.Open()
if ($Parameters) {
foreach ($arg in $Parameters.GetEnumerator()){
$cmd.Parameters.AddWithValue($arg.Key, $arg.Value) | Out-Null;
}
}
$reader = $cmd.ExecuteReader()
if ($reader.Read()) {
[string[]]$columns = 0..($reader.FieldCount-1) |
% { if ($reader.GetName($_)) { $reader.GetName($_) } else { "(no name $_)" } }
do {
$obj = #{}
0..($reader.FieldCount-1) | % { $obj[$columns[$_]] = $reader[$_] }
[PSCustomObject]$obj
} while ($reader.Read())
}
$reader.Dispose()
$cmd.Dispose()
$conn.Dispose()
}
You need a database table. Since there is no strict schema in question, I assume following, minimal:
$conn = 'Data Source=.;Initial Catalog=Test;Integrated Security=SSPI'
$createTestTable = #'
CREATE TABLE MyRequests
(
RequestId int,
DateLogged datetime
)
'#
Invoke-Sql $conn $createTestTable
There is no sample data, I assume folders named 1, 2, 3, .. 7 and matching records in SQL database:
1..7 | % {
Invoke-Sql $conn 'INSERT MyRequests VALUES (#id,#value)' #{id=$_;value=[DateTime]::Now.AddDays(-$_)}
mkdir $_
}
Table should contain following records (dates may differ):
RequestId DateLogged
----------- -----------------------
1 2018-09-23 14:47:49.113
2 2018-09-22 14:47:49.130
3 2018-09-21 14:47:49.137
4 2018-09-20 14:47:49.140
5 2018-09-19 14:47:49.140
6 2018-09-18 14:47:49.143
7 2018-09-17 14:47:49.147
Then, final solution:
#get deleted id's using OUTPUT clause
$older = Invoke-Sql $conn 'DELETE FROM MyRequests OUTPUT deleted.RequestId WHERE DateLogged<#date' #{date=[DATETime]::Now.AddDays(-4)}
#foreach id in returned set, delete corresponding folder
$older | select -ExpandProperty RequestId | % { rm $_ }

Index was outside the bounds of the array error when processing .svd file

clear
# Powershell scripts used to mask Private Personal Information on data files.
# path to PERM file
$path = "MYURL\ABS.SVD"
# get file content
$content = Get-Content $path
$content | ForEach-Object {
$mask50 = 'X' * 50 # variables to makes fields by letter according to length
$mask30 = 'Y' * 30
$mask20 = 'A' * 20
$mask08 = 'Z' * 8
$mask05 = 'B' * 5
$mask16 = 'C' * 16
# if statement, if the string is at position 0-10, and begins with
# 'STARTDELIV' then run replace statement
if ($_.Substring(0,10) -eq 'STARTDELIV') {
$SplitString = $_.Split('|')
$SplitString[3] = $mask30 # Fields : To Fax Name
$SplitString[4] = $mask20 # Fields : To fax Number
$SplitString[9] = $mask50 # Fields : To EMail Address
$SplitString -join '|'
} else {
$_
}
} | Out-File "MYURL\ABS-Output.svd" -Encoding ASCII
I have a script which masked certain fields in a data file (fields seperated by '|'). I have done a lot of these and they work fine. However, when I copy this exact file to another folder to use a different data file (.svd) I get the "Index was outside the bounds of the array", I've seen a few other threads on this but none really answer my query.
Curious as to why it still works in a particular folder location but not the other (I am changing the input and output urls to direct to the new folder).
Error below:
Index was outside the bounds of the array.
AtMYURL-Masked-Fields.ps1:28 char:3
+ $SplitString[3] = $mask30 #To Fax Name
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], IndexOutOfRangeException
+ FullyQualifiedErrorId : System.IndexOutOfRangeException
Answer provided by #lit
"There is at least one record starting with STARTDELIV that does not have enough fields. find /I "STARTDELIV" "MYURL\ABS.SVD" "

PowerShell variables not being expanded in string?

I have written some PowerShell
param (
[Parameter(Mandatory=$true)][string]$company,
[Parameter(Mandatory=$true)][string]$output
)
...
$objRecordset.Open("Select Col1, Col2, Col3 From $company_Table1.DBF", $objConnection,$adOpenStatic,$adLockOptimistic)
I am running it using
.\Test.ps1 -company A -output C:\test.txt
but for some reason the $company variable isn't being expanded even though it's in "quotes"?
Exception calling "Open" with "4" argument(s): "File '.dbf' does not exist."
At line:17 char:1
+ $objRecordset.Open("Select Col1, Col2, Col3 From $company_Table1. ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ComMethodTargetInvocation
When I hardcode it as A_Table1.dbf it works fine ...
The error message it telling you that PowerShell is parsing $company_Table1 as the name of the variable; _ is not acting as a delimiter.
You need to tell PowerShell where the variable name being and ends using curl braces {}
${company}_Table1.DBF
.DBF stands for database file.
You are trying to do a select on the database and not on a table in that database.
So it makes sense this isn't working.
it should be Select * from TBL_Whatever LIMIT 0,1 ....
So you first need to open a connection to the DB file itself and then do your select against a TABLE in that DB.

Loop through a query in powershell TFS

I am new to TFS and powershell scripting.As a part of my assignment I got a task in which
I have an array with TFS workitem ID's and I need to loop through and
get the details of those id's and display the result.
As a part of it I tried as shown below
$TFSSERVER = "https://server"
$WId = #("1", "2", "3", "4")
$arr = #("")
$i = 0
Function Get-WorkItemData($WId)
{
foreach($Id in $WId)
{
$i++
$query = "SELECT [System.Id]
FROM WorkItemLinks " +
"WHERE [Source].[System.id] = $Id" +
"AND [System.Links.LinkType] = 'Parent'"
if($WId.length -ge $i)
{
$arr+= $query
}
}
$wiData = tfpt query /collection:$TFSSERVER /wiql:$arr
$wiData | out-file "C:\Path"
}
Here is the error i get when i run the script
TFPT.EXE : Expecting end of string. The error is caused by ½SELECT╗.
At line:28 char:22
+ $wiData = tfpt <<<< query /collection:$TFSSERVER /wiql:$b
+ CategoryInfo : NotSpecified: ( Expecting end ...ed by ½SELECT╗.:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
Can anyone please help me to how to resolve the error and get list of all details. Please help me.
I do not know if this will resolve your error, but you at the very least have syntax issues on the line that starts with $query= as well as the following line. To fix this I would probably go with long single lined string (which in effect is what you are doing as is) or a HereString (if your executable allows for that).
A single lined string would work for you, and while it is long and a bit more troublesome to read it does avoid issues like what you are running into.
$query = "SELECT [System.Id] FROM WorkItemLinks WHERE [Source].[System.id] = $id AND [System.Links.LinkType] = 'Parent'"
A HereString is formatted similarly to what you have above, but may not be compatible with your application. It is formatted where the first line is #" with nothing after it, then however many lines of text that you want, then a line starting with "#. So in your case it would look like:
$query = #"
SELECT [System.Id]
FROM WorkItemLinks
WHERE [Source].[System.id] = $Id
AND [System.Links.LinkType] = 'Parent'
"#
This may not work for your needs, as I don't know if your TFPT.EXE application will accept a multi-line string as input for the query (and have serious doubts about it allowing it).
If you really, really want to continue formatting it like you have you can continue to do so, you just need to fix the first and second lines of that section at the least:
$query = "SELECT [System.Id] " +
"FROM WorkItemLinks " +
"WHERE [Source].[System.id] = $Id " +
"AND [System.Links.LinkType] = 'Parent';"

Concatenating PowerShell variables

$timeout = new-timespan -Minutes 1
$sw = [diagnostics.stopwatch]::StartNew()
$path = "d:\powershell\test.csv"
"Processor Load, Available Memory(MB), Max Memory(Bytes)" >> $path
while ($sw.elapsed -lt $timeout)
{
$a = gwmi -query "Select * from win32_processor"
$b = gwmi -query "Select * from win32_perfrawdata_perfOS_memory"
$c = gwmi -query "Select * from win32_physicalmemory"
$date = Get-Date -format s
$a.loadpercentage + "," + $b.availableMbytes + "," + $c.capacity >> $path
start-sleep -seconds 5
}
So I'm just looking to get a minute long snapshot of what's going on. I'm not just opening this in perfmon for reasons. Basically I'd expect to get a comma-delimited output in the CSV file mentioned. It works for a single variable, but when I try to add a second variable, or text I get the following error.
Cannot convert value ", test" to type "System.Int32". Error: "Input string was not in a
correct format."
At D:\powershell\VDIPerfMon.ps1:14 char:21
+ $a.loadpercentage + <<<< ", test" >> $path
+ CategoryInfo : NotSpecified: (:) [], RuntimeException
+ FullyQualifiedErrorId : RuntimeException
How can I fix this problem?
When you use the + operator PowerShell looks on the left hand side to determine the resulting type of the expression. It is seeing an int on the left of the + and a string (that can't be converted to an int) on the right. Try it this way:
"$($a.loadpercentage), $($b.availableMbytes), $($c.capacity)" >> $path
Also where you write your headers, you might not want to append i.e. in order to overwrite old attempts:
"Processor Load, Available Memory(MB), Max Memory(Bytes)" > $path
The error is because $a.loadpercentage is an int. You are then trying to add an int and a string.
One workaround is to explicitly call .ToString()
$a.loadpercentage.ToString() + "," + $b.availableMbytes.ToString() + "," + $c.capacity.ToString() >> $path
Another way is the PowerShell array join operator. It is quick easy, and types do not matter:
($a.loadpercentage, $b.availableMbytes, $c.capacity) -join "," |
Add-Content $path
Yet another way is with a string formatter. This will easily let you control the precision and display of each value:
'{0},{1},{2}' -f $a.loadpercentage, $b.availableMbytes, $c.capacity