Increment variable in Foreach-Object - sql

I want insert some AD attributes with PowerShell into a SQL table. So far so good:
$insert = #'
INSERT INTO [mdb].[dbo].[import](id,userid)
VALUES ('{0}','{1}')
'#
Try {
$connectionString = 'Data Source=serverdb;Initial Catalog=mdb;Integrated Security=SSPI'
$conn = New-Object System.Data.SqlClient.SqlConnection($connectionString)
$conn.Open()
$cmd = $conn.CreateCommand()
$counter = 0
Get-ADUser -Filter * -SearchBase "OU=company,DC=company,DC=state,DC=de" | Select #{Name="ID";Expression={ $global:counter; $global:counter++ }},SamAccountName |`
ForEach-Object {
$cmd.CommandText = $insert -f $counter,$_.SamAccountName
$cmd.ExecuteNonQuery()
}
$conn.Close()
}
Catch {
Throw $_
}
The output from get-ADUser is right, but the insert throws an error, that the primary key has duplicates. The incrementing must be wrong.
Can anybody help? THANKS!

ID is starting from 1 and not 0, is this normal ?
Furthermore, why are you creating an user defined property (ID) and don't use it ?
You can avoid the ugly global scope too (starting counter from 0 here) :
#### $counter = 0 <--- No more usefull
Get-ADUser -Filter * -SearchBase "OU=company,DC=company,DC=state,DC=de" | Select SamAccountName |`
ForEach-Object -Begin { $counter = 0 } -Process {
$cmd.CommandText = $insert -f $counter,$_.SamAccountName
$cmd.ExecuteNonQuery()
$counter++
}
I am not an SQL specialist, so this is probably not an anwser. But, you have here a nicer code ;)

You should always use parameters when inserting data with SQL. (Why?) In short: It's more secure, more performant, more robust and easier to use.
Put parentheses around the operation (++$counter) to return the value after increasing it. (Use $counter++ if you want zero-based ids, ++$counter if you want 1-based ids.)
$cmd.CommandText = "
INSERT INTO [mdb].[dbo].[import](id,userid)
VALUES (#id, #userId)
"
$counter = 0
Get-ADUser -Filter * -SearchBase "OU=company,DC=company,DC=state,DC=de" | foreach {
$cmd.Parameters.Clear()
$cmd.Parameters.AddWithValue("id", (++$counter))
$cmd.Parameters.AddWithValue("userId", $_.SamAccountName)
$cmd.ExecuteNonQuery()
}

Related

What is returned from a SQL query into a PowerShell variable?

Here is the function I have setup that works just fine to send queries to a SQL database from PowerShell and return the results (the results are what I don't quite understand)
function Invoke-SQL
{
param (
[string]$server,
[string]$database,
[string]$Query
)
$connectionString = "Data Source=$server; " +
"Integrated Security=SSPI; " +
"Initial Catalog=$database"
$connection = new-object
system.data.SqlClient.SQLConnection($connectionString)
$command = new-object system.data.sqlclient.sqlcommand($Query, $connection)
$connection.Open()
$adapter = New-Object System.Data.sqlclient.sqlDataAdapter $command
$dataset = New-Object System.Data.DataSet
$adapter.Fill($dataSet) | Out-Null
$connection.Close()
$dataSet.Tables
}
If I run a query such as the one below (it returns no results, meaning there were no records that existed that matched the condition) why does it return nothing when I just put in $results? Why is the result 'Table' when I do Write-Host $results ? See below
PS>$results = Invoke-SQL -server 'servername' -database 'DBname' -Query "SELECT * FROM [DBname].[dbo].[TableName] WHERE UserID = 'x' AND ComputerName = 'x'"
PS>$results
PS>Write-Host $results
Table
When no records are found I thought it would be equal to "" or $null but it is not upon testing
$null test
PS>If ($results -eq $null) {
>> write-host "Null"}else{
>> write-host "Not Null"
>> }
Not Null
"" test
PS>If ($results -eq "") {
>> write-host "Empty"}else{
>> write-host "Not Empty"
>> }
If someone could explain this to me, and what options I might have in order to check if a query returns no results, that would be great!
Read the comments on the question post for more details.
In order to see if records were returned or not, this will return the number of rows (records) returned. Credit to #Bill_Stewart.
($results | Measure-Object).Count
#Tomalak provided a helpful link.
#BaconBits had this helpful tip to get the type of an object
$results.GetType().FullName
# or
$results | Get-Member
Thank you all for your help.

Insert Microsoft Updates into Database

I'm trying to modify this script so that it inserts the installed updates into an SQL Server database table.
$conn = New-Object System.Data.SqlClient.SqlConnection
$conn.ConnectionString = "Data Source=sqlserver; Initial Catalog=updates; Integrated Security=SSPI;"
$conn.Open()
$cmd = New-Object System.Data.SqlClient.SqlCommand
$cmd.Connection = $conn
$cmd = $conn.CreateCommand()
$wu = new-object -com “Microsoft.Update.Searcher”
$totalupdates = $wu.GetTotalHistoryCount()
$all = $wu.QueryHistory(0,$totalupdates)
$OutputCollection= #()
Foreach ($update in $all){
$Regex = “KB\d*”
$KB = $string | Select-String -Pattern $regex | Select-Object { $_.Matches }
$output = New-Object -TypeName PSobject
$output | add-member NoteProperty “HotFix ID” -value $KB.‘ $_.Matches ‘.Value
$output | add-member NoteProperty “Title” -value $string
$OutputCollection += $output
$cmd.CommandText += "INSERT INTO dbo.updates (hotfixid, hotfixdescription) VALUES ('$($kb.'$_.Matches'.Value)', ('$($string)'))"
}
$cmd.ExecuteNonQuery()
$conn.close()
At the moment, I'm getting correct number of rows for updates in sql server but it isn't showing hotfixid and in hotfix descriptien columns there is a only one update in all rows.
Thanks!
Do the INSERTs inside the loop. I would, however, recommend that you use prepared statements instead of building the SQL statements via string concatenation. Also, there's no need to build $OutputCollection objects when you're not using it anywhere.
Something like this should work:
...
$wu.QueryHistory(0, $totalupdates) | % {
$KB = $_.Title | ? { $_ -match '(KB\d+)' } | % { $matches[1] }
$cmd = $conn.CreateCommand()
$cmd.CommandText = "INSERT INTO dbo.updates (hotfixid, hotfixdescription) " +
"VALUES (#id, #descr)"
$cmd.Parameters.AddWithValue("#id", $KB)
$cmd.Parameters.AddWithValue("#descr", $_.Title)
$cmd.Prepare()
$cmd.ExecuteNonQuery()
}
...
Untested, though, since I don't have an SQL Server at hand. I also suspect that there's a more efficient way to handle the prepared statements, but I'm not that familiar with SQL Server.

Reading txt content into variable SQL

I have a very simple question. My purpose here to retrieve login names from a txt file into a variable into SQL and query the SQL table while predicating against that same variable.
So for example:
the txt file would have:
forde
blain
martin
Alex
so the idea to feed each name to a variable and output the designated computer name.
Declare #loginName varchar (25)
--open the file
--while the end of the file has not reached, read each line and place the name into #loginName variable
select *
from computerinfo
where loginname = #loginname
I don't necessarily need to bulk import to a temp table at this point.
Thanks.
i had to do this some weeks ago and the simple way i found was Powershell.
I had no SSIS else it's the best of course.
# You may want to adjust these
function Invoke-Sqlcmd2
{
[CmdletBinding()]
param(
[Parameter(Position=0, Mandatory=$true)] [string]$ServerInstance,
[Parameter(Position=1, Mandatory=$false)] [string]$Database,
[Parameter(Position=2, Mandatory=$false)] [string]$Query,
[Parameter(Position=3, Mandatory=$false)] [string]$Username,
[Parameter(Position=4, Mandatory=$false)] [string]$Password,
[Parameter(Position=5, Mandatory=$false)] [Int32]$QueryTimeout=600,
[Parameter(Position=6, Mandatory=$false)] [Int32]$ConnectionTimeout=15,
[Parameter(Position=7, Mandatory=$false)] [ValidateScript({test-path $_})] [string]$InputFile,
[Parameter(Position=8, Mandatory=$false)] [ValidateSet("DataSet", "DataTable", "DataRow")] [string]$As="DataRow"
)
if ($InputFile)
{
$filePath = $(resolve-path $InputFile).path
$Query = [System.IO.File]::ReadAllText("$filePath")
}
$conn=new-object System.Data.SqlClient.SQLConnection
if ($Username)
{ $ConnectionString = "Server={0};Database={1};User ID={2};Password={3};Trusted_Connection=False;Connect Timeout={4}" -f $ServerInstance,$Database,$Username,$Password,$ConnectionTimeout }
else
{ $ConnectionString = "Server={0};Database={1};Integrated Security=True;Connect Timeout={2}" -f $ServerInstance,$Database,$ConnectionTimeout }
$conn.ConnectionString=$ConnectionString
#Following EventHandler is used for PRINT and RAISERROR T-SQL statements. Executed when -Verbose parameter specified by caller
if ($PSBoundParameters.Verbose)
{
$conn.FireInfoMessageEventOnUserErrors=$true
$handler = [System.Data.SqlClient.SqlInfoMessageEventHandler] {Write-Verbose "$($_)"}
$conn.add_InfoMessage($handler)
}
$conn.Open()
$cmd=new-object system.Data.SqlClient.SqlCommand($Query,$conn)
$cmd.CommandTimeout=$QueryTimeout
$ds=New-Object system.Data.DataSet
$da=New-Object system.Data.SqlClient.SqlDataAdapter($cmd)
[void]$da.fill($ds)
$conn.Close()
switch ($As)
{
'DataSet' { Write-Output ($ds) }
'DataTable' { Write-Output ($ds.Tables) }
'DataRow' { Write-Output ($ds.Tables[0]) }
}
} #Invoke-Sqlcmd2
$files = #(get-childitem "filelocationformultiplefile" -include *.txt -exclude *.bak -recurse | where-object {($_.LastWriteTime -le (Get-Date).AddDays(-0))-and ($_.psIsContainer -eq $false)})
if ($files -ne $NULL)
{
for ($idx = 0; $idx -lt $files.Length; $idx++)
{
$file = $files[$idx]
$Query = #"
Bulk INSERT db.dbo.tbl from '$file' with (FirstRow = 1, FieldTerminator ='";', RowTerminator = '\n')
"#
Invoke-sqlcmd2 -ServerInstance "servername" -Database "db" -Query $Query
}
}
And i'm not crazy, except the 10 ending lines everything else is coming from a microsoft official blog.
You don't need everything, powershell is present on every computer, it requires to save this in a file with extension ps1 and configure the 4 variables $fileout2 -> $tablename

How can I have a powershell loop that echos echo on the same line?

Currently I have the follow ps that reads a list of user names and then echos it.
The username file is the following
username1
username2
username3
The ps script is the following
$userNames = (Get-Content usernames.txt)# | Sort-Object
$userID=0
$userNames.Count
echo "FROM table WHERE (userID ='"
For ($i =1; $i -le ($userNames.Count - 1); $i++)
{
echo $userNames[$userID] "' OR userID='"
$userID++
}
echo $userNames[$userNames.Count - 1] "'"
I am hoping to get this to echo (and eventually write to a text file) all on the same line.
FROM table WHERE (userID = 'username1' OR userID = 'username2' OR userID = 'username3'
How would I go about solving this?
What you are looking for is:
Write-Host "Blah" -NoNewLine
I would probably re-write the script like this to avoid having to use the For...Loop
$userNames = (Get-Content usernames.txt) | Sort-Object
$count = 0
Write-Host "FROM table WHERE (" -NoNewLine
$userNames |% {
Write-Host "userID='$_'" -NoNewLine
if(++$count -ne $userNames.Length){
Write-Host " OR " -NoNewLine
}
else {
Write-Host ")"
}
}
This script will also take advantage of another nice feature of PowerShell, which is variable substitution in string literals. For-EachObject automatically sets $_ to be the current object during the iteration, and PowerShell will automatically parse variables in string literals and substitute their values.
Also... I just realized the entire thing can be reduced to the following:
$userNames = (Get-Content usernames.txt) | Sort-Object |% { "'$_'" }
Write-Host "FROM table WHERE UserID in ($([String]::Join(",",$userNames)))"
Which will produce the following query:
FROM table WHERE UserID in ('username1','username2','username3')
This is a much more pleasant script and query in my opinion :)

How to pass the parameter in SQL query from PowerShell

I have this code in PowerShell, that executes SQL query to UPDATE my table:
$Connection=new-object data.sqlclient.sqlconnection "server=server;database=mydb;trusted_connection=true;"
$Connection.open()
For ( $i = 0; $i -le $ActID.Length; $i ++ ) {
$cmd = New-Object System.Data.SqlClient.SqlCommand
$cmd.Connection = $Connection
$cmd.CommandText =
"
update Table
set Note = #PATH
"
$cmd.Parameters.Add("#PATH", $ActID[$i].Values) | Out-Null
$cmd.ExecuteNonQuery()
}
I tried to update the table with the variable defined in this string:
$cmd.Parameters.Add("#PATH", $ActID[$i].Values) | Out-Null
But when I execute the script the error log says that there is no value passed in $ActID[$i]
Are there other methods to pass parameters (variables) in powershell queries?
What could be the mistake:
$i -le $ActID.Length;
it should be probably
$i -lt $ActID.Length;
You could also use piping which simplifies the code:
$actId | % { ..... $cmd.Parameters.Add("#PATH", $_.Values) | Out-Null .... }
Besides that the property you use is Values - is it really what you wanted? Values looks like a collection of something. Maybe you wanted to use a single value.