Take value from SQL server and delete relevant folders - powershell

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 $_ }

Related

PowerShell SQL Query to Update Table

I am using PowerShell to run a SQL query. I then want to update another table based on information pulled from the query. I have tested my SQL query and update statements directly in the SQL Server Management Studio so I know that they work. The results of those tests show that there are over 800 records that should be updated. However, when I run the same query and update from within PowerShell, it only updates one record. I have mostly copied this script from another much larger script that was written in a similar format. But it appears that I am missing a ForEach loop (or something similar) but cannot figure out where to place it or how. Here is my script:
# Set the database connection strings
$Conn02 = new-object System.Data.SqlClient.SqlConnection "SERVER_INFORMATION"
$mySQL02 = New-Object System.Data.SqlClient.SqlCommand
$mySQL02.Connection = $Conn02
$Conn03 = new-object System.Data.SqlClient.SqlConnection "SERVER_INFORMATION"
$mySQL03 = New-Object System.Data.SqlClient.SqlCommand
$mySQL03.Connection = $Conn03
#Connect to the database to perform the query
$Conn02.Open()
$mySQL02.CommandText = "SELECT IDNUM, FNAME, LNAME
FROM TABLE1
WHERE STATUS = 'C'"
$SQL02 = $mySQL02.ExecuteReader()
WHILE($SQL02.Read()){
$NEWID = $SQL02['ID_NUM']
$FNAME1 = $SQL02['FNAME']
$LNAME1 = $SQL02['LNAME']
}
#Run the update
$Conn03.Open()
$mySQL03.CommandText =
"INSERT INTO TABLE2 (user_id,firstname,lastname)
VALUES ('$NEWIDme','$FNAME1','$LNAME1')"
Thank you for your time
As #jeroen said, you need to move the Insert statement into the while loop. Here is the code should look like:
#Connect to the database to perform the query
$Conn02.Open()
$mySQL02.CommandText = "SELECT IDNUM, FNAME, LNAME
FROM TABLE1
WHERE STATUS = 'C'"
$SQL02 = $mySQL02.ExecuteReader()
#Save reader into datatable
$Datatable = New-Object System.Data.DataTable
$Datatable.Load($SQL02)
#Close the connection
$Conn02.Close()
#Run the update
$Conn03.Open()
foreach($row in $dt){
$NEWID = $row['ID_NUM']
$FNAME1 = $row['FNAME']
$LNAME1 = $row['LNAME']
$mySQL03.CommandText =
"INSERT INTO TABLE2 (user_id,firstname,lastname)
VALUES ('$NEWIDme','$FNAME1','$LNAME1')"
$mySQL03.ExecuteNonQuery()
}
$Conn03.Close()
To prevent SQL Injection I suggest using Parameters when assigning values
foreach($row in $dt){
$NEWID = $row['ID_NUM']
$FNAME1 = $row['FNAME']
$LNAME1 = $row['LNAME']
$mySQL03.CommandText =
"INSERT INTO TABLE2 (user_id,firstname,lastname)
VALUES (#NEWIDme,#FNAME1,#LNAME1)"
$mySQL03.Parameters.Clear()
$Command.Parameters.AddWithValue('#NEWIDme',$NEWID) | Out-Null
$Command.Parameters.AddWithValue('#FNAME1',$FNAME1) | Out-Null
$Command.Parameters.AddWithValue('#LNAME1',$LNAME1) | Out-Null
$mySQL03.ExecuteNonQuery()
}

Insert data with a collection object into a SQL Server table

The code below does not error, it inserts into a SQL Server table with no issues. However the [ServicePrincipalNames] data is not inserted how I planned.
The value that gets inserted into the table is
Microsoft.ActiveDirectory.Management.ADPropertyValueCollection
What I am trying to insert is the value in that object collection which looks like this:
WSMAN/Server1Name
WSMAN/Server1Name.mx.ds.abc.com
TERMSRV/Server1Name
TERMSRV/Server1Name.mx.ds.abc.com
RestrictedKrbHost/Server1Name
HOST/Server1Name
RestrictedKrbHost/Server1Name.mx.ds.abc.com
HOST/Server1Name.mx.ds.abc.com
The code to do the insert is shown here below. How could I change this to have the insert put all the services in the column, separated by |?
$sqlServer='SomeServer'
$catalog = 'SomeDatabase'
$insert = #"
Insert into dbo.ADServers([Name],[OperatingSystem],[OperatingSystemVersion],[ipv4Address],[Created],[Deleted],[whenChanged],[Modified],[Description],[ServicePrincipalNames],[DisplayName],[Location],[DistinguishedName],[DNSHostName])
values('{0}','{1}','{2}','{3}','{4}','{5}','{6}','{7}','{8}','{9}','{10}','{11}','{12}', '{13}')
"#
$start = (Get-Date).ToString('MM/dd/yyyy hh:mm:ss tt')
$connectionString = "Data Source=$sqlServer;Initial Catalog=$catalog;Integrated Security=SSPI"
# connection object initialization
$conn = New-Object System.Data.SqlClient.SqlConnection($connectionString)
#Open the Connection
$conn.Open()
# Prepare the SQL
$cmd = $conn.CreateCommand()
#WMI ouput transformation to SQL table
Get-ADComputer -Filter {operatingSystem -Like 'Windows*server*2019*'} -Property * |`
Select Name,OperatingSystem,OperatingSystemVersion,ipv4Address,Created,Deleted,whenChanged,Modified,Description,ServicePrincipalNames,DisplayName,Location,DistinguishedName,DNSHostName |`
forEach-object{
$cmd.CommandText = $insert -f $_.Name, $_.OperatingSystem, $_.OperatingSystemVersion, $_.ipv4Address, $_.Created, $_.Deleted, $_.whenChanged, $_.Modified,$_.Description, $_.ServicePrincipalNames , $_.DisplayName,$_.Location,$_.DistinguishedName,$_.DNSHostName
$cmd.ExecuteNonQuery()
}
$end = (Get-Date).ToString('MM/dd/yyyy hh:mm:ss tt')
Write-Host $start
Write-Host $end
Ok after a more time googling and learning about out-string. In order to display objects i had to create an expression on that column and rewrite it as below. and it worked
In query replace the
ServicePrincipalNames
with
#{Label="ServicePrincipalNames";Expression={$_.ServicePrincipalNames -join ";" }}

Retrieve data from PostgreSQL using Powershell

I have been wrestling with database connection to PostgreSQL from Powershell. I finally am able to connect to and insert into the database. Now I can't figure out how to extract data from a DB select into a variable.
I'm not including my insert for the sake of clarity but will tack it onto this thread later as I know it was super hard to find and may be helpful to someone.
so here's my code:
# use existing 64 bit ODBC System DSN that we set up manually
$DBconn = New-Object -comobject ADODB.Connection
$DBconn.Open("PostgreSQL35W")
$theQuery = "select * from test1"
$theObject = $DBconn.Execute($theQuery) # $theObject is a System.__ComObject
$numRecords = $theObject.RecordCount
write-host "found $numRecords records" # getting -1
$theObject.MoveFirst() # throws no error
# $theValue = $theObject.DataMember # throws no error, but gives no result
$theValue = $theObject.Index[1] # throws "Cannot index into a null array"
write-host($theValue)
try this
replace "#database#" with your database name in $cnString
replace "#server_ip#" with your server ip address in $cnString
replace "#user#" with a valid user in $cnString and $user
replace "#pass#" with a valid pass in $pass
replace "#table#" with a valid table name of your db
replace 5432 with your db port
$cnString = "DRIVER={PostgreSQL Unicode(x64)};DATABASE=#database#;SERVER=#server_ip#;PORT=5432;UID=#user#;"
$user="#user#"
$pass="#pass#"
$conn = New-Object -comobject ADODB.Connection
$conn.Open($cnString,$user,$pass)
$recordset = $conn.Execute("SELECT * FROM #table# limit 1;")
while ($recordset.EOF -ne $True)
{
foreach ($field in $recordset.Fields)
{
'{0,30} = {1,-30}' -f # this line sets up a nice pretty field format, but you don't really need it
$field.name, $field.value
}
'' # this line adds a line between records
$recordset.MoveNext()
}
$conn.Close();
Via psql, which comes with postgresql
$dburl="postgresql://exusername:expw#exhostname:5432/postgres"
$data="select * from extable" | psql --csv $dburl | ConvertFrom-Csv
You must have psql in your path or reference it, its within e.g. C:\Program Files\PostgreSQL\12\bin. Should be able to type "psql" and see output within powershell.
As a warning, expect strings. E.g $data[0].age.GetType() would be string, despite being stored in the database as an integer. You can immediately cast it, cast it later, or hope powershell infers type correctly.
If you want to add back in type information can do e.g.:
$data = $data | %{[pscustomobject]#{name=$_.name;age=[int]$_.age}}
I ended up figuring it out - here's what I did
$conn = New-Object -comobject ADODB.Connection
# use existing 64 bit ODBC System DSN that we set up manually
$conn.Open("PostgreSQL35W")
$recordset = $conn.Execute("SELECT * FROM JobHistory")
while ($recordset.EOF -ne $True)
{
foreach ($field in $recordset.Fields)
{
'{0,30} = {1,-30}' -f # this line sets up a nice pretty field format, but you don't really need it
$field.name, $field.value
}
'' # this line adds a line between records
$recordset.MoveNext()
}
$conn.Close();
Exit
use the dot notation. You don't need to split the data.
$list = New-Object Collections.Generic.List[OnlineCourse]
foreach($element in $results)
{
$tempObj= New-Object OnlineCourse($element.id,$element.courseName,$element.completedRatio,$element.completedRatio,$element.lastActivity, $element.provider)
$list.add($tempObj)
}
I have a slightly different approach to #dog, I couldn't get the --csv to work, so I resorted to tuple only rows returned, then parse them into a List of Classes (which happen to be called OnlineCourses):
class OnlineCourse
{
[int]$id
[string]$email
[string]$courseName
[int]$completedRatio
[datetime]$lastActivity
[String]$provider
OnlineCourse([int]$id,
[string]$email,
[string]$courseName,
[int]$completedPerc,
[datetime]$lastActivity,
[String]$provider) {
$this.id = $id
$this.email = $email.Trim()
$this.courseName = $courseName.Trim()
$this.completedRatio = $completedPerc
$this.lastActivity = $lastActivity
$this.provider = $provider.Trim()
}
}
$connstr="postgresql://exusername:expw#exhostname:5432/postgres"
$data = "select * from onlinecourses" | .\psql -t $connstr
$list = New-Object Collections.Generic.List[OnlineCourse]
foreach ($field in $data) {
$id, $email, $courseName, $completedratio, $lastactivity, $provider = $field.split('|')
$course = [OnlineCourse]::new($id, $email, $courseName, $completedratio, $lastactivity, $provider)
$list.Add($course)
}
This is slightly adapted from another answer and it worked for me.
$dburl="postgresql://postgres:secret_pwd#database-host:5432/dbname"
$psqlPath = 'C:\Program Files\PostgreSQL\11\bin\psql.exe'
function Query {
param($Sql)
Write-Host $Sql
$rows = $Sql `
| &$psqlPath "-A" $dburl | ConvertFrom-Csv -Delimiter '|'
$result = #($rows | Select-Object -SkipLast 1)
Write-Host "-> " (ConvertTo-Json $result)
$result
}
$rows = Query "select ... from ..."

Unexplained Numeric Output from Powershell Script

We are working on updating scriptPath attributes in AD. We are updating employees in small groups (approximately 100-200 at a time). For this purpose I have created the following script.
$newScript = "foo.vbs"
# Load Employee List
$employeeList = Get-Content "NAM_logon_EmployeeList.txt"
$objDomain = New-Object System.DirectoryServices.DirectoryEntry( "LDAP://OU=Users,DC=foobar,DC=com" )
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.PageSize = 100
$objSearcher.SearchScope = "Subtree"
$colProplist = "scriptPath", "distinguishedName", "cn"
# Loop through Employee List and update the script value
ForEach ( $employee In $employeeList ) {
$objSearcher.Filter = "(&(objectCategory=person)(objectClass=user)(mail=$employee))"
Foreach ( $colProp in $colPropList ) {
$objSearcher.PropertiesToLoad.Add( $colProp )
}
$colResults = $objSearcher.FindAll()
ForEach ( $user In $colResults ) {
$ntuser = $user.Properties.Item("distinguishedName")
$myUser = $user.Properties.Item("cn")
Script to Pushout the change
$objUser = [ADSI]"LDAP://$($ntuser)"
$objUser.put( "scriptPath", $newScript )
$objUser.SetInfo()
echo "Script Added for $($myUser)"
}
}
The script works fine, but line 18:
$objSearcher.PropertiesToLoad.Add( $colProp )
Outputs numbers to the PowerShell window. One number for each Object and Attribute it adds.
0
1
2
Script Added for Smith, John
4
5
6
Script Added for Doe, Jane
etc.
I don't know why it's doing this. Anyone have any ideas?
It is just output that the command is outputting as it updates the object. A lot of .net objects will do this. If you don't want to see the output do the following:
$null = $objSearcher.PropertiesToLoad.Add( $colProp )
From the documentation found on MSDN:
Return Value
The zero-based index at which the new element is inserted.
So, the numbers you are seeing are the index at which the elements are being added.
For ignoring output like that, I like to do the following:
$objSearcher.PropertiesToLoad.Add( $colProp ) | Out-Null

Using Powershell against a DataReader's GetSchemaTable() method

I'm running a stored procedure and am trying to filter out which columns are returned by GetSchemaTable()
$reader = $cmd.ExecuteReader()
$schemaTable = $reader.GetSchemaTable();
foreach ($row in $schemaTable.Rows)
{
foreach ($column in $schemaTable.Columns)
{
write-host $column;
}
}
...shows me a whole bunch of column names I don't care about and 2 that i do:
ColumnName and ColumnOrdinal
How do I go about restricting output to just those to fields?
thx
$reader = $cmd.ExecuteReader()
$reader.GetSchemaTable() | Select ColumnName, ColumnOrdinal