How to implement Where-Object in SQL-Invoke command - sql-server

I am trying to filter some tables from my database which exists in my solution folder. I want to filter all tables that I am pulling from SQL Server:
$existingTables = "Table1", "Table2", "Table3", "Table4"
#getting all tables except existing ones
#SqlQuery = "SELECT name FROM sys.Tables order by name asc"
$filteredTables = ((Invoke-SQL -DataSource $ServerName -DatabaseName $DatabaseName -UserID $UserID -Password $Password -SqlCommand $SQLQuery).name | ? {$_ -notcontains $existingTables})
#$filteredTables returns all tables, including the existing ones...
I've tried $_.name and it is the same result.

You're using the operands of the -notcontains operator in the wrong order. The correct syntax is
reference_array -notcontains item
In your case:
$existingTables -notcontains $_
or
$existingTables -notcontains $_.Name
if you don't expand the property Name.
If you want to use the reference array as the second operand you must use the -notin operator:
$_ -notin $existingTables
However, that operator is not available prior to PowerShell v3.
Alternatively, you could add an exclude clause to your SQL statement, as #vonPryz suggested in the comments. Take care to not open yourself to SQL injection when doing that, though.
Don't do this:
$SQLQuery = #"
SELECT ...
FROM ...
WHERE name NOT IN ('$($existingTables[0])', '$($existingTables[1])', ...)
"#
Use a prepared statement (or "parameterized query" as Microsoft calls them). I don't recognize the cmdlet you're using, though (doesn't seem to be Invoke-Sqlcmd), so I can't tell you how to do it with that cmdlet.

Related

How to compare data from SQL to the one in a directory using PowerShell 5.1?

The two columns in the database MAFN and Version which when concatenated with a dot "." have the (almost the)same format as the folder names in the directory.
The data in col. MAFN is 123and Version is 1
The folders are named like:
001234.001,
000789.011
etc.
I was able to remove the leading zeros from the directory folder names.
I was also able to concatenate the data from the two columns.
I want to compare concatenated items from the query to the (new)folder names in the directory and if they match I want to do something.
The question is how do I compare the two, the concatenated data from SQL and the folder names.
I have tried storing the data from SQL in a variable but it doesn't work. Let's say the data is stored is $sqlData then Write-Host $sqlData gives the following output
System.Data.DataRow System.Data.DataRow System.Data.DataRow System.Data.DataRow System.Data.DataRow System.Data.DataRow System.Data.DataRow System.Data.DataRow System.Data.DataRow Syste m.Data.DataRow
The code is:
#SQL connection and concat columns
Invoke-Sqlcmd -ServerInstance "Sidney9.sidney.nii" -Database "NML_Sidney" -Query "SELECT TOP (10) MAFN,Version,(Convert(varchar(50),MAFN)+'.'+Convert(varchar(50),Version))
FROM [NML_Sidney].[dbo].[vNML_MAFN_CNCP_ByPartResource]"
#Split, trim, and join folder names to match concated data from SQL
$aidLibPath = "C:\Users\userName\Desktop\CNC_Transfer_Test_Folders\AidLib_Test\*"
Get-Item -Path $aidLibPath | ForEach-Object{
$splitFileName = $_.Name.Split('.')
$trimSplitFileName = $splitFileName.trimstart("0")-join(".")
write-host $trimSplitFileName
}
Basically, how do I store the data in a variable and loop over them to compare them?
I am using MSSQL 2005 and PowerShell 5.1 with SQL Server and SQLPS modules loaded.
Any help is appreciated.
String concatenation in TSQL is not required if you have a nice scripting language like PowerShell available.
How I would do this given the sql returns the values desired:
#SQL connection and concat columns
$dbResult = Invoke-Sqlcmd -ServerInstance "Sidney9.sidney.nii" -Database "NML_Sidney" -Query "SELECT TOP (10) MAFN,Version
FROM [NML_Sidney].[dbo].[vNML_MAFN_CNCP_ByPartResource]"
$dbFolders = $dbResult | ForEach-Object { "$( $_.MAFN ).$( $_.Version )" }
#Split, trim, and join folder names to match concated data from SQL
$aidLibPath = 'C:\Users\userName\Desktop\CNC_Transfer_Test_Folders\AidLib_Test'
$folders = ( Get-ChildItem -Path $aidLibPath -Directory ).Name | Where-Object {
$_.IndexOf( '.' ) -gt 0 } | ForEach-Object {
$parts = $_.Name -split '.'
"$( $parts[0].TrimStart('0') ).$( $parts[1].TrimStart('0') )"
}
# compare the lists to see what the intersect is
$result = Compare-Object -ReferenceObject $dbFolders -DifferenceObject $folders -IncludeEqual -ExcludeDifferent
if ( $result ) {
$result.InputObject
}

Powershell invoke-sqlcmd Printing The Wrong Output

I have a SQL query that is running as expected however when I try to use it in PowerShell 'Invoke-SqlCmd' module, the output comes out different than when querying the database. I noticed that there are quite a few questions regarding this module but I couldn't find one that is applicable to my case.
Query:
$SQLServer = "localhost"
$query = "SELECT Groups.[Name] AS AGname FROM sys.dm_hadr_availability_group_states States INNER JOIN master.sys.availability_groups Groups ON States.group_id = Groups.group_id WHERE primary_replica = ##Servername"
$HAGName = Invoke-Sqlcmd -query $query -ServerInstance $SQLServer -Database 'database'
if ($HAGName = !$null) {
write-host "Availability group name is $HAGName"
exit 0
}
else {
write-host "Failed to retrieve High Availability group name = [$HAGName]"
exit 1
}
Output in PowerShell: 'Availability group name is True'
Like I mentioned, when querying SQL Server directly I get the correct output. I tried using the 'OutputAs' switch but it didn't help.
Any help will be greatly appreciated.
All the pointers are in the comments on the question, but let me break it down systematically:
!$null is always $true in PowerShell: ! / -not, the logical NOT operator coerces $null to a Boolean, and since [bool] $null is $false, ! $null is $true.
$HAGName = !$null, due to using =, the assignment operator, therefore assigns $true to variable $HAGName.
To instead perform an equality comparison, use -eq, the equality operator.
Therefore, $null -eq $HAGName is what you meant to use (placing the $null on the LHS, for robustness - see the docs).
However, given PowerShell's implicit to-Boolean coercion rules (see the bottom section of this answer), you could simplify to if ($HAGName) { ... } in this case.
Therefore, a more PowerShell-idiomatic reformulation of your code is:
$SQLServer = 'localhost'
$query = 'SELECT Groups.[Name] AS AGname FROM sys.dm_hadr_availability_group_states States INNER JOIN master.sys.availability_groups Groups ON States.group_id = Groups.group_id WHERE primary_replica = ##Servername'
$HAGName = Invoke-Sqlcmd -Query $query -ServerInstance $SQLServer -Database database
if ($HAGName) {
Write-Verbose -Verbose "Availability group name is: "
# Output the System.Data.DataRow instance as-is,
# which also results in proper for-display formatting.
# If you just want the value of the .AGname property (column), use
# $HAGName.AGname instead.
$HAGName
exit 0
}
else {
Write-Warning "Failed to retrieve High Availability group name."
exit 1
}
Note:
The success case implicitly outputs the result, to the success output stream.
Write-Host is typically the wrong tool to use, unless the intent is to write to the display only, bypassing the success output stream and with it the ability to send output to other commands, capture it in a variable, or redirect it to a file. To output a value, use it by itself; e.g., $value instead of Write-Host $value (or use Write-Output $value, though that is rarely needed); see this answer
I've used a Write-Verbose call (whose output is quiet by default, here I've used -Verbose to force it to show) to provide optional supplemental / status information.
$HAGName now (implicitly) outputs the [System.Data.DataRow] instance returned by the Invoke-SqlCmd call as-is, which also results in proper display formatting - such instances do not stringify meaningfully when used in an expandable (interpolating string); they unhelpfully stringify to their type name, i.e. to verbatim System.Data.DataRow.
However, if you access a specific property (column) of the row, its value may stringify meaningfully, depending on its data type; in your case: `"Availability group name is $($HAGName.AGname)"
To include the usual for-display formatting inside a string - use something like "Availability group name is $($HAGName | Out-String)"

Replace backup location in SQL jobs

We are trying to replace a backup location in a SQL Backup Jobs step (running power shell through several servers)
Below is a PS script i would like to use it:
# $Server is a file with SERVERNAME names
$Jobs = Get-SQLAgentJob -ServerInstance
$Servers Foreach ($job in $Jobs.Where{$_.Name -like 'DatabaseBackup' -and $_.isenabled -eq $true}) {
foreach ($Step in $Job.jobsteps.Where{$_.Name -like 'DatabaseBackup'}) {
$Step.Command = $Step.Command.Replace("Directory = N'C:\Backup\oldname1\oldname2\SERVERNAME'", "Directory = N'C:\Backup2\newname1\newname2\SERVERNAME'")
$Step.Alter()
}
}
It seems like this should work. The only potential problems I see are the following:
named SQL instances: The $servers variable will need to have the servername\instancename format if not using the default instance name
Job and step names: If your job names and job step names are not exactly databasebackup, case excluded, then the -like operator combined with the exact string will not find a match. If the names contain the databasebackup string, you will be safer to use -match "databasebackup" or -like with asterisks on both sides of the string.
Otherwise, this code should just work provided there are not network connectivity or permissions issues.

Powershell script scripts on dbachecks to compare the MaxMemory of server listed in a table

Run checks against servers
Import-Module dbatools
Import-Module dbachecks
$Server = "AMCB123"
$Database = "DBA"
# Create recordset of servers to evaluate
$sconn = new-object System.Data.SqlClient.SqlConnection("server=$Server;Trusted_Connection=true");
$q = "SELECT DISTINCT servername FROM DBA.[dbo].[Server_Group] WHERE ID =1;"
$sconn.Open()
$cmd = new-object System.Data.SqlClient.SqlCommand ($q, $sconn);
$cmd.CommandTimeout = 0;
$dr = $cmd.ExecuteReader();
# Loop through the servers and build an array
while ($dr.Read()) {
Get-DbaMaxMemory -SqlServer $dr.GetValue(0) | Format-Table
}
$dr.Close()
$sconn.Close()
I have Listed the sql server(stage, prod, DR servers in a table as per the groups), Now I want to compare the servers with group id's to check wethere the servers(stage,prod, DR) with same group id is having same MAXMemory cofiguration or not.
For this I'm using the below powershell script can you please help me with this, I have created a table with all the servewith grop id.
Request to please help me with the loop thorugh the servers and build an array, so that I can run the MAXMEMORY powershell command to compare it using the group id for all servers.
I have collected all the servers details into a table dbo.server groups
the powershell script should iterate through the table by using the ID and check whether the servers in the ID group has same MAXMEMORY configuration ID server_name Environment
1 ABC0123 prod
1 ABC5123 stage
1 ABC4123 DR
2 DEF0123 prod
2 DEF5123 stage
2 DEF4123 DR
I'm trying to use a powershell script which will check and compare the MAXMEMORY configuration as per the ID(to check whether stage, prod, DR server of the same group_id have similar setting or not), if not then it will display a warning/message as group_ids servers are not configured similarly.
Please help me with the script
You're making this script longer than it needs to be. Also, you're using Format-Table prematurely - you should only use the Format-* functions for displaying final information to the user; they output strings, not properly typed data/variables that can be used down the line.
Use the tools that PowerShell and dbatools give you to get your server list, and then pass that list to Get-DbaMaxMemory as a collection.
import-module dbatools
$ServerList = Invoke-DbaSqlQuery -ServerInstance $Server -query "select distinct servername from dba.dbo.server_group where group_id = 1" | Select-Object -ExpandProperty servername;
Get-DbaMaxMemory -ServerInstance $ServerList | Select-Object SqlInstance, SqlMaxMB;
This will give you a list of your SQL instances and the memory they're configured to use. What you do after that...it's hard to say as you haven't clearly defined what you're looking for.
But this may not tell the full story. Wouldn't it be better to check the configured values and what you're currently running with? You can do that with Get-DbaSpConfigure.
import-module dbatools
$ServerList = Invoke-DbaSqlQuery -ServerInstance $Server -query "select distinct servername from dba.dbo.server_group where group_id = 1" | Select-Object -ExpandProperty servername;
Get-DbaSpConfigure -ServerInstance $ServerList | Select-Object ServerName,ConfiguredValue,RunningValue;
You can even create a computed column in that final Select-Object to tell you if the configured & running values differ.
If you just wanted to use dbachecks (which uses dbatools in the background) you can use
$ServerList = (Invoke-DbaSqlQuery -ServerInstance $Server -query "select distinct servername from dba.dbo.server_group where group_id = 1").servername
and
Invoke-DbcCheck -SQlInstance $ServerList -Check MaxMemory
Or you can set the configuration item app.computername and app.sqlinstance to your server list using
Set-DbcConfig -Name app.sqlinstance -Value $serverlist
Set-DbcConfig -Name app.computername -Value $serverlist
and then you can run this (or any other checks) using
Invoke-DbcCheck -Check MaxMemory

Join SQL query Results and Get-ChildItem Results

Background: I have a directory with a number of files that are imported to SQL server.
Task: Creating a PowerShell script which will pick up files within this directory and use the filenames as in the SQL query.
Ultimate objective: To display SQL results besides the filenames but the resultset being displayed should also show files having no entries in SQL server. Something like RIGHT JOIN in SQL server queries.
Powershell Code
$files = Get-ChildItem -Recurse -Force $filePath -ErrorAction SilentlyContinue | Where-Object { ($_.PSIsContainer -eq $false) } | Select-Object Name
$Server = "Loadv1"
$DB = "LoadDB"
$dbResults = #()
ForEach ($file in $files)
{
$fileName = $file.name
write-host $fileName
if($fileName.Length -gt 1)
{
$Query = "
SELECT FileName,CurrentStatus
FROM LogStatus
WHERE FileName LIKE '$fileName%'
"
# Write-host $Query
}
$dbResults += Invoke-Sqlcmd -ServerInstance $Server -Database $DB -Query $Query
}
$dispResults = $dbResults,$file
$dispResults | Format-Table -autosize
Work done so far: I have been able to fetch the file names using Get-ChildItem and loop them to get the query results. However, the result I am currently getting does not show the files that don't have corresponding entry in SQL server table
Current Result
OperationalLanding20150622061502.dat
OperationalLandingAudit20150622061502.dat
OperativeThird_Party_System20150616090701.dat
FileName CurrentStatus
OperationalLandingAudit20150622061502.dat SSIS Package Complete
OperativeThird_Party_System20150616090701.dat SSIS Package Complete
Expected Result
OperationalLanding20150622061502.dat
OperationalLandingAudit20150622061502.dat
OperativeThird_Party_System20150616090701.dat
FileName CurrentStatus
OperationalLanding20150622061502.dat NULL
OperationalLandingAudit20150622061502.dat SSIS Package Complete
OperativeThird_Party_System20150616090701.dat SSIS Package Complete
Hoping I was able to explain my requirement above.
OK so if the SQL query does not have results then NULL is returned and, in essence, nothing is added to the $dbResults array. Instead lets append the results to a custom object. I don't know what PowerShell version you have so I needed to do something that I know should work. I also don't use the SQL cmdlets much so I had to guess for some of this.
$files = Get-ChildItem -Recurse -Force $filePath -ErrorAction SilentlyContinue |
Where-Object {$_.PSIsContainer -eq $false -and $_.Length -gt 1} |
Select-Object -ExpandProperty Name
$Server = "Loadv1"
$DB = "LoadDB"
$files | ForEach-Object{
write-host $_
$Query = "
SELECT FileName,CurrentStatus
FROM LogStatus
WHERE FileName LIKE '$_%'
"
$Results = Invoke-Sqlcmd -ServerInstance $Server -Database $DB -Query $Query
$props = #{Name = $_}
If($Results){
$props.CurrentStatus = $Results.CurrentStatus
} Else {
$props.CurrentStatus = "Null"
}
New-Object -TypeName PSCustomObject -Property $props
} | Format-Table -autosize
What this does is create a custom object that contains the results of the sql query (Which I did not change for reasons stated above). If there are no results returned we use the string "null" as a filler.
I cleaned up how you generated the $files variable by making is a simple string array with -Expand and moved the length condition there as well.
You should now have all the expected results. I say should since I am assuming what the return object looks like.
$Query = "
SELECT isNull(A.FileName, b.FileName) FileName,ISNULL(A.CurrentStatus,B.CurrentStatus) CurrentStatus
FROM LogStatus A
Right JOIN (SELECT '$filename' FileName,NULL CurrentStatus) B
ON a.Filename like '$filename%'
"
This should pad out the filenames for you. A little tough to prototype since it's in powershell but I might be able to come up with a sql fiddle to prove it.
EDIT
Answer edited, with sql fiddle:
http://sqlfiddle.com/#!3/12b43/9
Obviously, since you're in a cursor, we can only prove one query at a time.

Resources