Invoke-SqlCmd generates an exception in foreach loop? - sql-server

I am running 2 different sql query one after the other to get the details from database using Invoke-Sqlcmd but getting an exception.
The WriteObject and WriteError methods cannot be called from outside the overrides of the BeginProcessing, ProcessRecord, and EndProcessing methods, and they can only be called from within the same thread. Validate that the cmdlet makes these calls correctly
Script:
$ok = "true"
$serverName = $env:COMPUTERNAME
$query1 = "SELECT * FROM sys.server_audits WHERE name = 'Login'"
$query2 = "SELECT * FROM sys.dm_server_audit_status WHERE name = 'Login' AND status_desc = 'STARTED'"
try
{
Write-Host "Getting running Sql Server Instance (online)"
$sqlInstances = (Get-Service -Name MSSQL$* | Where-Object { $_.status -eq "Running" }).Name
$sqlInstancesCount = $sqlInstances.Count
Write-Host $eventID "$sqlInstancesCount Sql Server Instance found"
if($sqlInstancesCount)
{
foreach($item in $sqlInstances)
{
$count = 0
$instanceName = $item -replace "MSSQL\$", ""
$sqlInstanceFullname = Join-Path -Path $serverName -ChildPath "\" | Join-Path -ChildPath $instanceName
Write-Host "Sql Server instance fullname - $sqlInstanceFullname"
while($count -lt 3)
{
Write-Host "Clear existing connection."
[System.Data.SqlClient.SqlConnection]::ClearAllPools()
Write-Host "Executing query - $query1"
$result1 = (Invoke-Sqlcmd -Query $query1 -ServerInstance $sqlInstanceFullname)
Start-Sleep -Seconds 30
Write-Host "Clear existing connection."
[System.Data.SqlClient.SqlConnection]::ClearAllPools()
Write-Host "Executing query - $query2"
$result2 = (Invoke-Sqlcmd -Query $query2 -ServerInstance $sqlInstanceFullname)
if($null -eq $result1)
{
$message = "$serverName - The SQL Server [Login] does not exist."
$ok = "false"
$count = $count + 1
}
elseif($null -eq $result2)
{
$message = "$serverName - The SQL Server [Login] is disabled."
$ok = "false"
$count = $count + 1
}
else
{
$message = "SQL Login is enabled and running."
$count = 3
$ok = "true"
}
}
}
}
else
{
$ok = "false"
throw "$serverName - SQL Server Instances might be offline or not exist!"
}
}
catch
{
throw $_
}
I have tried enclosing Invoke-Sqlcmd in brackets but still getting same exception so is there a way to remove existing thread or close the connection before executing another query.

Related

Error when executing SQL-Query from Powershell

I always get an error when executing this script:
Incorrect syntax near '#values'
I have the following function to execute my SQL statement:
$Server = "Server"
$Database = "Database"
$i = 0
function ExecuteSQLQuery(){
$Connection = New-Object System.Data.SqlClient.SqlConnection
$Connection.ConnectionString = "server='$Server';database='$Database'; trusted_connection=true;"
$Connection.Open()
$query = #
"INSERT INTO [Database].[dbo].[Folder](FolderPath,UserGroup,Permission,AccessControllType) VALUES #values
"#
$command = $connection.CreateCommand()
$command.CommandText = $query
$command.Parameters.Add("#values", $values)
$command.ExecuteNonQuery()
}
And this is the rest of the code in which I am getting the Data for the INSERT
$RootPath = "\\server\folder1\folder2\folder3"
$Folders = dir $RootPath -recurse | where {$_.psiscontainer -eq $true}
#root folder permission
$ACLs = get-acl $RootPath | ForEach-Object { $_.Access }
Foreach ($ACL in $ACLs) {
#Add-Content -Value $OutInfo -Path $OutFile
$FolderPath = $RootPath
$User = $ACL.IdentityReference
$Permission = $ACL.FileSystemRights
$AccessControllType = $ACL.AccessControlType
if($i -gt 50) {
Invoke-sqlcmd #params -Query $InsertResult
$i=0
$values = ""
}
elseif($i -eq 0) {
$values += "('$FolderPath','$User','$Permission','$AccessControllType')"
$i ++
}
else {
$values += ",('$FolderPath','$User','$Permission','$AccessControllType')"
$i ++
}
}
#subfolders
foreach ($Folder in $Folders) {
$ACLs = get-acl $Folder.fullname | ForEach-Object { $_.Access }
Foreach ($ACL in $ACLs) {
$FolderPath = $Folder.Fullname
$User = $ACL.IdentityReference
$Permission = $ACL.FileSystemRights
$AccessControllType = $ACL.AccessControlType
$IsInherited = $ACL.IsInherited
if ($i -gt 50) {
ExecuteSQLQuery
$i=0
$values = ""
}
elseif ($i -eq 0) {
$values += "('$FolderPath','$User','$Permission','$AccessControllType')"
$i ++
}
else {
$values += ",('$FolderPath','$User','$Permission','$AccessControllType')"
$i ++
}
}
}
I am new to Powershell so I don't now if the error is because of my code or if the $values contains something Powershell or SQL can't handle.
Do you guys have any ideas?

Array returned has an unexpected element in Powershell

An array returned from a function has an unexpected element appended to it, and I cannot understand how to fix it. A help will be so much appreciated.
The array in function A, for some unknown reason is different to the array in function B that calls the function A.
function functionA(...)
{
[...]
$status = (,#("reserved",$proj,$id));
Write-Host "Status in FunctionA to FunctionB: "$status
[...]
return $status
}
I get from the Write-Host above: Status in FunctionA to FunctionB: reserved B hfuhfkhec8u8b7hf4smeu43gn4
function functionB(...)
{
[...]
$funcA = functionA
Write-Host "Status in FunctionB from FunctionA: "$funcA
[...]
}
I get from the Write-Host above: Status in FunctionB from FunctionA: 1 reserved B hfuhfkhec8u8b7hf4smeu43gn4. You can observe the $status in functionA has not the value 1, so why I get the value 1 appended in the array? What may I do to fix it?
Please check the complete code bellow, where FunctionA is CheckReservation and FunctionB is PeriodicCheckIn
PS: I noted that if I comment the function InformationMsgBox to do not call if in CheckReservation, then the code works.
function CheckReservation($headers){
$events = GetCalendarEvents($headers)
$users = Get-IniFile ..\ini\Users.ini
$user = $users.$env:UserName.email
$error = "There is not any confirmed reservation for this machine."; $status = (,#("NoReservations",$error));
$calendarId = Get-IniFile ..\ini\Computers.ini
$calendarId = $calendarId.$env:Computername.calendarId
foreach($item in $events.items)
{
if($item.status -match "confirmed")
{
$attender = $item.attendees.email |
Where-Object {$_ -contains $user}
$calendar = $item.attendees |
Where-Object {$_.email -contains $calendarId}
$calendarEmail = $calendar.email
$calendarStatus = $calendar.responseStatus
$organizer = $item.organizer.email | Where-Object {$_ -contains $user}
if(($attender -match $user) -or ($item.organizer.email -match $user))
{
if(($calendarStatus -match "accepted") -or ($item.organizer.email -match $calendarId))
{
$current = [Xml.XmlConvert]::ToString((get-date),[Xml.XmlDateTimeSerializationMode]::Local)
if(((get-date $current) -ge (get-date $item.start.dateTime)) -and ((get-date
$current) -lt (get-date $item.end.dateTime)))
{
$timespan = NEW-TIMESPAN –Start (get-date $current) –End (get-date $item.end.dateTime);
$timeexpire = [int]$timespan.TotalMinutes;
Write-Host "Minutes to expire reservation: "$timeexpire;
$Timers = Get-IniFile ..\ini\Timers.ini;
$WarningBeforeExp = $Timers.Timers.WarningBeforeExp_min;
if($timeexpire -le $WarningBeforeExp)
{
$msg = message(21);
Write-Host $msg;
InformationMsgBox $msg 10;
}
$proj=$item.summary
$id=$item.id
$status = (,#("reserved",$proj,$id));
Write-Host "Status in FunctionA to FunctionB: "$status
} else { $msg = "Reservation expired or schedule time mismatch."; $status = (,#("outOfTime",$msg)); }
}
} else { $msg = "You are not an attender for the current reservation for this machine."; $status = (,#("IsNotAttender",$msg));}
} else { $msg = "There is not any confirmed reservation for this machine. You can use it until someone makes a reservation."; $status = (,#("NoReservations",$msg));}
}
return $status
}
function PeriodicCheckIn ($OAuth2){
$checkin = $false
$Timers = Get-IniFile ..\ini\Timers.ini
$CheckInPeriodicity = $Timers.Timers.CheckInPeriodicity_min
for($i=1;;$i++)
{
$timeout = IdleTimeout
if(-not $timeout)
{
$funcA = CheckReservation $OAuth2
Write-Host "Status in FunctionB from FunctionA: "$funcA
$reservation = $funcA
if ($reservation[0] -match "reserved")
{
$project = $reservation[1]
$id = $reservation[2]
Write-Host "Reservation found"
Write-Host "Project: "$project
Write-Host "Id: "$id
if(-not $checkin)
{
storageData "CheckInOut" $OAuth2 "CheckIn" $project "" $false
$checkin = $true
$msg = message(15)
Write-Host $msg
InformationMsgBox $msg -1
}
else
{
storageData "updatetime" $OAuth2 "LastUpdate" $project "" $false
}
}
elseif($i -eq 1)
{
Write-Host "Reservation not found"
$Availability = CheckAvailability $OAuth2 "10"
Write-Host "Availability for now: "$Availability
if($Availability -match "Yes")
{
$msg = message(16)
$msgBoxInput = QuestionMsgBox $msg 30
if($msgBoxInput -eq 6)
{
$project = GetProject "FromUser"
CreateReservation $OAuth2 $project "10"
storageData "CheckInOut" $OAuth2 "CheckIn" $project "" $false
$checkin = $true
$msg = message(15)
Write-Host $msg
InformationMsgBox $msg -1
}
else
{
$leave = $true;
}
} else
{
$leave = $true;
}
}
else
{
Write-Host "O pau é aqui?1 $reservation[1]"
$msg = message(18, $reservation[1]);
Write-Host "O pau é aqui?2 $reservation[1]"
StopMsgBox $msg -60
$leave = $true;
}
} else {$leave = $true;}
if($leave -eq $true){ return $true}
Write-Host "CheckIn $i"
start-sleep -Seconds ([double]$CheckInPeriodicity*60)
}
}
function message($index,$txt){
$LastWarning_min = Get-IniFile ..\ini\Timers.ini
$LastWarning_min = $LastWarning_min.Timers.LastWarning_min/60
$ManualAuthTimeout_min = $LastWarning_min.Timers.ManualAuthTimeout_min
Write-Host "Next message index: $index"
$arrMessage =
"[MSG000] It is not possible to identify if this current PC is Workstation or Buildstation machine.",
"[MSG001] Shutting down the system.",
"[MSG002] You do not have approriate access to this function. Get in contact with lab support.",
"[MSG003] An error occurred. Verify if there are at least two not empty .txt file, if the path is correct or if you have write permission to the path $txt.",
"[MSG004] Attempt to delete a folder that does not exist: $txt.",
"[MSG005] Mapping failed. Make sure your connection can reach the networking that remote path ""$txt"" belongs.",
"[MSG006] Team not specified for the user $env:UserName. Please make sure the user is registered.",
"[MSG007] Connection failed. Make sure the PC can reach FIATAUTO networking then try again.",
"[MSG008] Error while Import-Module.",
"[MSG009] Error on OAuth 2.0 for Google APIs Authorization.",
"[MSG010] Error on Method: spreadsheets.values.get (writing) for Google Sheets API request.",
"[MSG011] Error on Method: spreadsheets.values.get (reading) for Google Sheets API request.",
"[MSG012] Error on Method: scripts.run for Google App Script API request.",
"[MSG013] Error on Events: get for Google Calendar API request.",
"[MSG014] Error on Events: list for Google Calendar API request.",
"[MSG015] Your access has been granted to work on the project: $global:project.",
"[MSG016] No reservation was found. Would you like to make a short reservation?",
"[MSG017] Permission to shared drives mismatch. Select OK to proceed for manual authorization within $ManualAuthTimeout_min minutes or select CANCEL for Windows logoff now.",
"[MSG018] No valid reservation found. $txt",
"[MSG019] Inactive time exceeded the inactivity limit. Select OK within 60 seconds to avoid the current Windows session logoff.",
"[MSG020] Save your files now. Shutting down the system within $LastWarning_min seconds.",
"[MSG021] Your reservation is close to expire!"
return $arrMessage[$index]
}
function InformationMsgBox($msg, $msg_timeout){
$sh = New-Object -ComObject "Wscript.Shell"
return $sh.Popup($msg,$msg_timeout,$global:AppTitle,1+64)
}
As you have noted yourself, the InformationMsgBox is to blame. Every command returning output gets added to the pipeline.
return $sh.Popup($msg,$msg_timeout,$global:AppTitle,1+64) returns the 1 you see added to your array. You can suppress it's output by sending it to Out-Null.
function InformationMsgBox($msg, $msg_timeout){
$sh = New-Object -ComObject "Wscript.Shell"
return $sh.Popup($msg,$msg_timeout,$global:AppTitle,1+64) | Out-Null
}

New-Object : Cannot bind parameter 'Property'. Cannot convert the "" value of type PSCustomObject to type IDictionary

I'm having a hard time converting results from Invoke-SqlCmd to a PSCustomobject.
So, I input my query and SQL server, then I run the Invoke-SqlCmd function, and then I try to add data from that (the database logical name, and the autogrowth status) to my PSCustomobject, so I can return it back to my modules public function.
$sqlinstance = "---"
$query = "---"
if ($sqlInstance -match "---") {
$dbAutogrowIGP = Invoke-Sqlcmd -Query $query -ServerInstance $sqlInstance
if ($dbAutogrowIGP.Growth -gt 100 -and $dbAutogrowIGP.Growth -lt 500) {
$autogrowStatus = [PSCustomObject]#{
'SQL_Instance' = $dbAutogrowIGP.LogicalName
'Check' = "Autogrow"
'Status' = "green"
'Status_reason' = ""
}
New-Object -Type Dictionary -Property $autogrowStatus
}
foreach ($db in $dbAutogrowIGP) {
if ($db.Growth -lt 100 -or $db.Growth -gt 500 -and $db.Growth -notlike "%") {
$autogrowStatus = [PSCustomObject]#{
'SQL_Instance' = $db.LogicalName
'Check' = "Autogrow"
'Status' = "red"
'Status_reason' = "$($db.LogicalName) has autogrowth set to $($db.Growth)."
}
New-Object -Type Dictionary -Property $autogrowStatus
}
if ($db.Growth -like "%") {
$autogrowStatus = [PSCustomObject]#{
'SQL_Instance' = $db.LogicalName
'Check' = "Autogrow"
'Status' = "yellow"
'Status_reason' = "$($db.LogicalName) has autogrowth set percentually, it should be an absolute number."
}
New-Object -Type Dictionary -Property $autogrowStatus
}
}
}
return $autogrowStatus
I've debugged it, and I've noticed it fails on the New-object call. I've tried both Dictionary and PSObject/PSCustomObject - however neither works
In my other functions, this works as expected, however in those, I'm using dbatools to make a call.
$getLogSizeIGP = Get-DbaDbLogSpace -sqlInstance $sqlInstance
if ($getLogSizeIGP.LogSize.Gigabyte -lt 10 -and $getLogSizeIGP.LogSpaceUsedPercent -lt 50) {
$logStatus = #{
'SQL_Instance' = $getLogSizeIGP.SqlInstance
'Check' = "Log_size"
'Status' = [gmEnvStatuses]::green
'Status_reason' = ""
}
New-Object -Type PSObject -Property $logStatus
}
How would I go about solving this issue?
This is the whole error message:
New-Object : Cannot bind parameter 'Property'. Cannot convert the "#{SQL_Instance=Maintenance_log; Check=Autogrow; Status=red; Status_reason=Maintenance_log has autogrowth set to 10%.}" value of type "System.Management.Automation.PSCustomObject" to type
"System.Collections.IDictionary".
At C:\Users\---\Desktop\autogrowth.ps1:50 char:55
+ New-Object -Type Dictionary -Property $autogrowStatus
+ ~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [New-Object], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.NewObjectCommand
Thanks!
The easiest way of collecting this data is by capturing it all at the beginning of the if ($sqlInstance -match "---") { statement and simply output the PsCustomObjects without trying to convert them.
Something like
$sqlinstance = "---"
$query = "---"
$autogrowStatus = if ($sqlInstance -match "---") {
$dbAutogrowIGP = Invoke-Sqlcmd -Query $query -ServerInstance $sqlInstance
if ($dbAutogrowIGP.Growth -gt 100 -and $dbAutogrowIGP.Growth -lt 500) {
# output the object to be captured in the $autogrowStatus variable
[PSCustomObject]#{
'SQL_Instance' = $dbAutogrowIGP.LogicalName
'Check' = "Autogrow"
'Status' = "green"
'Status_reason' = ""
}
}
foreach ($db in $dbAutogrowIGP) {
if ($db.Growth -lt 100 -or $db.Growth -gt 500 -and $db.Growth -notlike "%") {
[PSCustomObject]#{
'SQL_Instance' = $db.LogicalName
'Check' = "Autogrow"
'Status' = "red"
'Status_reason' = "$($db.LogicalName) has autogrowth set to $($db.Growth)."
}
}
if ($db.Growth -like "%") {
[PSCustomObject]#{
'SQL_Instance' = $db.LogicalName
'Check' = "Autogrow"
'Status' = "yellow"
'Status_reason' = "$($db.LogicalName) has autogrowth set percentually, it should be an absolute number."
}
}
}
}
return $autogrowStatus
The variable $autogrowStatus will become an array of PSCustomObjects to handle in the rest of your functions.
Hope this helps

Backup all databases fails on external server

I have the following script to backup all databases from an SQL instance
This works perfectly from my local PC but when I run it on an external server it fails with the following:
master backup failed.
Exception calling "SqlBackup" with "1" argument(s): "Backup failed for Server 'SERVER2016'."
$SQLInstance = "Localhost"
$BackupFolder = "D:\Backups\"
$timeStamp = Get-Date -Format yyyy_MM_dd_HHmmss
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoExtended") | Out-Null
$srv = New-Object ("Microsoft.SqlServer.Management.Smo.Server") $SQLInstance
$dbs = New-Object Microsoft.SqlServer.Management.Smo.Database
$dbs = $srv.Databases
foreach ($Database in $dbs) {
$Database.Name
$bk = New-Object ("Microsoft.SqlServer.Management.Smo.Backup")
$bk.Action = [Microsoft.SqlServer.Management.Smo.BackupActionType]::Database
$bk.BackupSetName = $Database.Name + "_backup_" + $timeStamp
$bk.Database = $Database.Name
$bk.CompressionOption = 1
$bk.MediaDescription = "Disk"
$bk.Devices.AddDevice($BackupFolder + "\" + $Database.Name + "_" + $timeStamp + ".bak", "File")
try {
$bk.SqlBackup($srv)
} catch {
$Database.Name + " backup failed."
$_.Exception.Message
}
}

run query against multiple databases on different server

I'm working on a PowerShell script to run a query against multiple servers and databases were the idea is to dynamically add server and databases to an array and execute them.
Currently I'm stuck at the last part where everything is combined. I can add the servers but not the databases.
What I am trying to achieve: PowerShell script with MFP GUI to run a query against multiple MSSQL servers which all contain identical database (with different data) but the databases have different names like Sql_Data-Node1, SqlData-Node2, etc.
Problem I encounter: I managed to add the servers dynamically to an array and when I run a query I get the proper response. In this case I used the master database an I made it static (-database 'master'). When I try to do the same with the databases (add them to an array) I get an error:
Invoke-Sqlcmd : Cannot validate argument on parameter 'Database'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
At C:\Users\master\Documents\MULTSCRIPT\MultiQueryV0.6.ps1:368 char:109
+ ... ame -Password $PassWord -ServerInstance $_[0] -Database $_[1] -Query ...
+ ~~~~~
+ CategoryInfo : InvalidData: (:) [Invoke-Sqlcmd], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.SqlServer.Management.PowerShell.GetScriptCommand
My code:
#Create EMPTY ARRAY for Databases
$script:DBSet = New-Object System.Collections.ArrayList
#==========================================================================
$window.Master.add_Checked({
$script:Master = 'master' #Add IP to Variable
$script:DBSet.Add("$script:Master") #Add Variable to Array
[System.Object]$sender = $args[0]
[System.Windows.RoutedEventArgs]$e = $args[1]
})
$window.Master.add_Unchecked({
$script:Master = $null
$script:DBSet.Remove("$script:Master")
[System.Object]$sender = $args[0]
[System.Windows.RoutedEventArgs]$e = $args[1]
})
#==========================================================================
$window.DataNodes.add_Checked({
$script:DB01 = 'Database01'
$script:DBSet.Add("$script:DB01")
[System.Object]$sender = $args[0]
[System.Windows.RoutedEventArgs]$e = $args[1]
})
$window.DataNodes.add_Unchecked({
$script:DB01 = $null
$script:DBSet.Remove("$script:DB01")
[System.Object]$sender = $args[0]
[System.Windows.RoutedEventArgs]$e = $args[1]
})
#Create EMPTY ARRAY For Servers
$script:ServerAddress = New-Object System.Collections.ArrayList
#Add action to Checkbox====================================================
$window.DB00.add_Checked({
$script:SRV00 = '190.168.1.8' #Add IP to Variable
$script:ServerAddress.Add("$script:SRV00") #Add Variable to Array
[System.Object]$sender = $args[0]
[System.Windows.RoutedEventArgs]$e = $args[1]
})
$window.DB00.add_Unchecked({
$script:SRV00 = $null
$script:ServerAddress.Remove("$script:SRV00") #Remove Variable to Array
[System.Object]$sender = $args[0]
[System.Windows.RoutedEventArgs]$e = $args[1]
})
#==========================================================================
$window.DB01.add_Checked({
$script:SRV01 = '192.168.1.9'
$script:ServerAddress.Add("$script:SRV01")
[System.Object]$sender = $args[0]
[System.Windows.RoutedEventArgs]$e = $args[1]
})
$window.DB01.add_Unchecked({
$script:SRV01 = $null
$script:ServerAddress.Remove("$script:SRV01")
[System.Object]$sender = $args[0]
[System.Windows.RoutedEventArgs]$e = $args[1]
})
#Collect Credentials#======================================================
$credential = Get-Credential
$UserName = $credential.UserName.Replace('\','')
$PassWord = $credential.GetNetworkCredential().password
#Collect From Input Fields#================================================
$window.Button.add_Click({
$SQLQuery = $window.Query.Text.ToString()
$Server = $script:ServerAddress
$DatabaseSet = $script:DBSet
$instances = #( #($Server, $DatabaseSet) )
$instances | ForEach{
Invoke-Sqlcmd -AbortOnError `
-Username $UserName`
-Password $PassWord`
-ServerInstance $_[0]`
-Database $_[1]`
-Query $SQLQuery`
-QueryTimeout 30 |
Out-GridView -Title $_[0]
}
[System.Object]$sender = $args[0]
[System.Windows.RoutedEventArgs]$e = $args[1]
})
It seems that the following solution works.
Credit goes to:Mutiple Variables in Foreach Loop [Powershell]
$window.Button.add_Click(
{ $DataBase = $window.DataBase.Text.ToString()
$SQLQuery = $window.Query.Text.ToString()
$Server = $ServerAddress.getenumerator()
$Database = $DBSet.getenumerator()
while($Server.MoveNext() -and $Database.MoveNext()){
Invoke-Sqlcmd -AbortOnError -Username $UserName -Password $PassWord -ServerInstance $Server.Current -Database $Database.Current -Query $SQLQuery -QueryTimeout 30 | Out-GridView -Title $Server.Current}

Resources