How to script all child/parent tables given a specific table name? - sql-server

I need to provide my Powershell script a specific table name and then generate the DDL in sorted order for all child/parent tables. In the image below, I have attached an example schema where the green bubble indicates what the script is currently producing and the red bubble indicates the DDL that is currently missing for the desired output.
$InstanceName = 'SERVER_NAME'
[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | out-null
$server = New-Object ('Microsoft.SqlServer.Management.Smo.Server') $InstanceName;
$db = $server.Databases["DATABASE_NAME"]
# Create an instance of the dependency walker class
$dw = New-Object Microsoft.SqlServer.Management.Smo.DependencyWalker($server)
$tables = $db.Tables['tbl_C1']
# Define an empty array to store the sorted tables
$sortedTables = #()
# Loop through the tables
foreach ($table in $tables) {
# Get the dependencies for the current table
$dependencies = $dw.DiscoverDependencies($table.Urn, $false)
# Add the current table to the array of sorted tables
$sortedTables += $table
# Loop through the dependencies
foreach ($dependency in $dependencies) {
# Check if the dependency is a table
if ($dependency.Type -eq "Microsoft.SqlServer.Management.Smo.Table") {
# Check if the dependency is not already in the array of sorted tables
if ($sortedTables -notcontains $dependency.Urn.GetSmoObject()) {
# Add the dependency to the array of sorted tables
$sortedTables += $dependency.Urn.GetSmoObject()
}
}
}
}
# Loop through the sorted tables and script them out
foreach ($table in $sortedTables) {
$scripter = new-object Microsoft.SqlServer.Management.Smo.Scripter($server)
$scripter.Options.ScriptDrops = $false
$scripter.Options.WithDependencies = $true
$scripter.Options.Indexes = $false
$scripter.Options.DriAll = $true
$scripter.Options.ScriptData = $false
$scripter.Options.SchemaQualify = $true
$scripter.Options.ScriptSchema = $true
$scripter.Options.IncludeHeaders = $true
$scripter.Script($table)
}

Related

Powershell populate word table from an array

I have a PS script that will import a csv into several arrays and I need it to populate a table in word. I am able to get the data into the arrays, and create a table with headers and the correct number of rows, but cannot get the data from the arrays into the table. Doing lots of google searches led me to the following code. Any help is greatly appreciated.
Sample of My_File.txt
Number of rows will vary, but the header row is always there.
component,id,iType,
VCT,AD-1234,Story,
VCT,Ad-4567,DR,
$component = #()
$id = #()
$iType =#()
$vFile = Import-CSV ("H:\My_file.txt")
$word = New-Object -ComObject "Word.Application"
$vFile | ForEach-Object {
$component += $_.components
$id += $_.id
$iType +=_.iType
}
$template = $word.Documents.Open ("H:\Test.docx")
$template = $word.Document.Add()
$word.Visible = $True
$Number_rows = ($vFile.count +1)
$Number_cols = 3
$range = $template.range()
$template.Tables.add($range, $Number_rows, $Number_cols) | out-null
$table = $template.Tables.Item(1)
$table.cell(1,1).Range.Text = "Component"
$table.cell(1,2).Range.Text = "ID"
$table.cell(1,3).Range.text = "Type"
for ($i=0; $i -lt; $vFile.count+2, $i++){
$table.cell(($i+2),1).Range.Text = $component[$i].components
$table.cell(($i+2),2).Range.Text = $id[$i].id
$table.cell(($i+2),3).Range.Text = $iType[$i].iType
}
$Table.Style = "Medium Shading 1 - Accent 1"
$template.SaveAs("H:\New_Doc.docx")
Don't separate the rows in the parsed CSV object array into three arrays, but leave the collection as-is and use the data to fill the table using the properties of that object array directly.
I took the liberty of renaming your variable $vFile into $data as to me at least this is more descriptive of what is in there.
Try
$data = Import-Csv -Path "H:\My_file.txt"
$word = New-Object -ComObject "Word.Application"
$word.Visible = $True
$template = $word.Documents.Open("H:\Test.docx")
$Number_rows = $data.Count +1 # +1 for the header
$Number_cols = 3
$range = $template.Range()
[void]$template.Tables.Add($range, $Number_rows, $Number_cols)
$table = $template.Tables.Item(1)
$table.Style = "Medium Shading 1 - Accent 1"
# write the headers
$table.cell(1,1).Range.Text = "Component"
$table.cell(1,2).Range.Text = "ID"
$table.cell(1,3).Range.text = "Type"
# next, add the data rows
for ($i=0; $i -lt $data.Count; $i++){
$table.cell(($i+2),1).Range.Text = $data[$i].component
$table.cell(($i+2),2).Range.Text = $data[$i].id
$table.cell(($i+2),3).Range.Text = $data[$i].iType
}
$template.SaveAs("H:\New_Doc.docx")
When done, do not forget to close the document, quit word and clean up the used COM objects:
$template.Close()
$word.Quit()
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($template)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($word)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()

Cleaner PowerShell code for adding to array

I'm trying to create a cleaner code for my script. I'm first defining the array, which will be used to check if the user account has been already queried. Currently the code has two nested if statements, which I want to combine into one if possible. Here is my code:
function addUser {
[cmdletbinding()]
Param(
[Parameter(Mandatory=$true)]
[String]$userName
,
[Parameter(Mandatory=$true)]
[String]$userDomain
,
[Parameter(Mandatory=$true)]
[String]$userAccount
)
Begin {}
Process {
$adAttribute = (Get-ADUser -Identity $userName -Server ($userDomain + "<FQDN HERE>") -Properties "<AD ATTRIBUTE NAME HERE>")."<AD ATTRIBUTE NAME HERE>"
Write-Output (
[pscustomobject]#{
userName = $userName
userDomain = $userDomain
userAccount = $userAccount
<AD ATTRIBUTE NAME HERE> = $adAttribute
}
)
}
End {}
}
$userList = #()
Get-Content $sourceFile | % {
<SOME CODE HERE>
if ($userList.Length -eq 0) {
$userList += addUser -userName $userName -userDomain $userDomain -userAccount $userAccount
}
else {
if (-not $userList.userAccount.Contains($userAccount)){
$userList += addUser -userName $userName -userDomain $userDomain -userAccount $userAccount
}
}
$userAdAttribute = $userList.Where({$_.userAccount -eq $userAccount})."<AD ATTRIBUTE NAME HERE>"
}
<SOME CODE HERE>
As you can see the following code is repetative:
$userList += <SOME FUNCTION CODE HERE>
but I cannot figure out how to make it cleaner, as in the beginning the array is empty and I cannot validate it before adding, so I'm wondering if anyone can share a tip?
I think you could do better using a Hashtable or HashSet for that instead of adding to an array with += (which needs to rebuild the entire array in memory on every addition)
$userList = #{}
$userAccount = <SOME CODE HERE>
# add the $userAccount you get from the code block above to the hash
if (-not $userList.Contains($userAccount)) {
# this user has not been processed, so do that here
<CODE TO PROCESS USER>
# next add to the Hashtable
$userList[$userAccount] = $true # the value is not important
}
Using a HashSet:
# on PowerShell below 5.0 use
# $userList = New-Object -TypeName 'System.Collections.Generic.HashSet[String]' -ArgumentList ([StringComparer]::InvariantCultureIgnoreCase)
$userList = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::InvariantCultureIgnoreCase)
$userAccount = <SOME CODE HERE>
# add the $userAccount you get from the code block above to the hash
if (-not $userList.Contains($userAccount)) {
# this user has not been processed, so do that here
<CODE TO PROCESS USER>
# next add to the Hashtable
[void]$userList.Add($userAccount)
}
So, if it is not just about a string holding the userAccount (SamAccountName I gather), but about objects with multiple properties, you then need to use the Hashtable approach:
$userList = #{}
$userObject = <THE CODE THAT CALLS FUNCTION 'addUser' AND RETURNS THE USER OBJECT>
# add the $userObject you get from the code block above to the hash
if (-not $userList.Contains($userObject.userAccount)) {
# this user has not been processed, so do that here
<CODE TO PROCESS USER>
# next add to the Hashtable
$userList[$userObject.userAccount] = $userObject # the value is the userAccount Object
}

SMO to script just the database and it settings (not the objects in it)

I have put together a nice PowerShell script to script out the objects (tables, functions, sprocs etc) from a database, limiting it to the ones in a list.
But I am stuck trying to find a way to script the database itself. Each time I do that, it seems to try to script out the whole database (it is way to large for that to go well).
Assuming I have a $db variable that is a reference to my database, how can I use SMO to script out that database, creating it with the same Properties and DatabaseScopedConfigurations, but none of the actual objects in it?
Update:
For reference here is my current script. It takes a server and database name and will script out all the objects found in a file called DbObjectsList.txt (assuming they are in the database). But this does not actually make the database. The database I am running this on is a legacy one, and it has a bunch of odd options set. I would like to preserve those.
$serverName = "MyServerName"
$databaseName = "MyDbName"
$date_ = (date -f yyyyMMdd)
$path = ".\"+"$date_"
# Load the Sql Server Management Objects (SMO) and output to null so we don't show the dll details.
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') > $null
# Setup the scripting options
$scriptOptions = new-object ('Microsoft.SqlServer.Management.Smo.ScriptingOptions')
$scriptOptions.ExtendedProperties = $true
$scriptOptions.AnsiPadding = $true
$scriptOptions.ClusteredIndexes = $true
# Dri = Declarative Referential Integrity
$scriptOptions.DriAll = $true
$scriptOptions.Triggers = $true
$scriptOptions.NoCollation = $false
$scriptOptions.SchemaQualify = $true
$scriptOptions.ScriptSchema = $true
$scriptOptions.EnforceScriptingOptions = $true
$scriptOptions.SchemaQualifyForeignKeysReferences = $true
$scriptOptions.NonClusteredIndexes = $true
$scriptOptions.Statistics = $true
$scriptOptions.Permissions = $true
$scriptOptions.OptimizerData = $true
# get a reference to the database we are going to be scripting from
$serverInstance = New-Object ('Microsoft.SqlServer.Management.Smo.Server') $serverName
$db=$serverInstance.Databases | Where-Object {$_.Name -eq $databaseName}
$dbname = "$db".replace("[","").replace("]","")
$dbpath = "$path"+ "\"+"$dbname" + "\"
if ( !(Test-Path $dbpath))
{
$null=new-item -type directory -name "$dbname"-path "$path"
}
# Load the list of db objects we want to script.
$listPath = ".\DbObjectList.txt"
if ((Test-Path $listPath))
{
$dbListItems = Get-Content -Path $listPath
}
else
{
throw "Could not find DbObjectst.txt file (it should have a list of what to script)."
}
# Setup the output file, removing any existing one
$outFile = "$dbpath" + "FullScript.sql"
if ((Test-Path $outFile)){Remove-Item $outFile }
$typeDelimiter = "=========="
foreach ($dbListItem in $dbListItems)
{
# Let the caller know which one we are working on.
echo $dbListItem
if ($dbListItem.StartsWith($typeDelimiter))
{
# Pull the type out of the header
$startIndex = $typeDelimiter.Length;
$stopIndex = $dbListItem.LastIndexOf($typeDelimiter)
$type = $dbListItem.Substring($startIndex, $stopIndex - $startIndex).Trim()
continue;
}
if ($type -eq $null)
{
throw "Types not included DbObjectsList.txt. Add types before groups of objects, surrounded by " + $typeDelimiter
}
foreach ($dbObjectToScript in $db.$type)
{
$objName = "$dbObjectToScript".replace("[","").replace("]","")
$compareDbListItem = "$dbListItem".replace("[","").replace("]","")
if ($compareDbListItem -eq $objName)
{
"-- " + $dbListItem | out-File -Append $outFile
$dbObjectToScript.Script($scriptOptions)+"GO" | out-File -Append $outFile
}
}
}

Scripting DBs backup through Powershell

I have created a PowerShell script that takes the back up of entire structure of database. When it comes to jobs backup, I cannot find a possible solution to that.
$v = [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO')
if ((($v.FullName.Split(','))[1].Split('='))[1].Split('.')[0] -ne '9')
{
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMOExtended') | out-null
}
[System.Reflection.Assembly]:: LoadWithPartialName('Microsoft.SqlServer.SmoEnum') | out-null
set-psdebug -strict # catch a few extra bugs
$ErrorActionPreference = "stop"
$My = 'Microsoft.SqlServer.Management.Smo'
$srv = new-object ("$My.Server") $ServerName # attach to the server
foreach($sqlDatabase in $srv.databases)
{
$databaseName=$sqlDatabase.name
if ($databaseName.count)
{
$scripter = new-object ("$My.Scripter") $srv # create the scripter
$scripter.Options.ToFileOnly = $true
# we now get all the object types except extended stored procedures
# first we get the bitmap of all the object types we want
$all =[long]
[Microsoft.SqlServer.Management.Smo.DatabaseObjectTypes]:: all -bxor
[Microsoft.SqlServer.Management.Smo.DatabaseObjectTypes]:: ExtendedStoredProcedure
# and we store them in a datatable
$d = new-object System.Data.Datatable
# get everything except the servicebroker object, the information schema and system views
$d = $srv.databases[$databaseName].EnumObjects([long]0x1FFFFFFF -band $all) | Where-Object {$_.Schema -ne 'sys'-and $_.Schema "information_schema"
#Saving it in a directory
}
}
This scripts takes the back up of the db but take the structural back up of msdb. I studied Microsoft.SqlServer.SMO that says it has a job server agent and job collection function but it doesn't seem to work.
For jobs, use the JobServer.Jobs collection. You can similarly script other server-level objects.
Below is an example.
$jobsCollection = $srv.JobServer.Jobs
$scriptingOptions = New-Object Microsoft.SqlServer.Management.Smo.ScriptingOptions
$scriptingOptions.IncludeIfNotExists = $true
$scriptingOptions.AppendToFile = $false
$scriptingOptions.ToFileOnly = $true
foreach ($job in $jobsCollection) {
Write-Host "Scripting $($job.Name)"
$scriptingOptions.FileName = "C:\ScriptFolder\Jobs.sql"
$job.Script($scriptingOptions)
$scriptingOptions.AppendToFile = $true
}
Although the answer given by Dan helped me but it wasn't creating a script in the folders. It was just creating folders with the jobs names. So, I did something like this :
foreach($sqlDatabase in $srv.JobServer.Jobs)
{ $databaseName=$sqlDatabase.name
write-host("I am her '$databaseName' ");
$scripter = new-object ("$My.Scripter") $srv # create the scripter
$scripter.Options.ToFileOnly = $true
$d = new-object System.Data.Datatable
$d=$srv.JobServer.Jobs[$databaseName]
$d| FOREACH-OBJECT {
trap [System.Management.Automation.MethodInvocationException]{
write-host ("ERROR: " + $_) -Foregroundcolor Red; Continue
}
# for every object we have in the datatable.
$SavePath="$($DirectoryToSaveTo)\$($ServerName)\$($databaseName)\$($_.DatabaseObjectTypes)"
# create the directory if necessary (SMO doesn't).
if (!( Test-Path -path $SavePath )) # create it if not existing
{Try { New-Item $SavePath -type directory | out-null }
Catch [system.exception]{
Write-Error "error while creating '$SavePath' $_"
return
}
}
# tell the scripter object where to write it
$scripter.Options.Filename = "$SavePath\$($_.name -replace '[\\\/\:\.]','-').sql";
# Create a single element URN array
$UrnCollection = new-object ('Microsoft.SqlServer.Management.Smo.urnCollection')
$URNCollection.add($_.urn)
# and write out the object to the specified file
$scripter.script($URNCollection)
}
}

Powershell: Excel combine worksheets into a single worksheet

I have some web script that I've adapted to run 7 T-SQL queries and output the results into 1 Excel workbook, one worksheet per query. I've just been asked if I can combine all 7 worksheets into one.
Here's my sample code which does copy a worksheet, however the entire column(s) are selected instead of just the UsedData. Also, the first worksheet's data on the destination worksheet is replaced by the second worksheets data.
Questions: Would it be simpler to get Powershell to output the 7 queries into One Excel Worksheet separated by two blank rows? Or modify the existing Powershell script to create the 7 worksheets then combine them into one?
Code is not pretty! I also have been really lost using $Excel = New-Object -ComObject excel.application followed by $Excel | Get-Member to explore how to get PowerShell to work with Excel. References on MSDN are usually for VB or C languages and I can't translate that into PowerShell.
--Edit, add code that stores 7 Query results in an array and outputs to the console. The data is correct but I'm just unsure how to approach piping that data into a single Excel Worksheet.
$docs = "C:\Temp\SQL\test.xlsx"
If (Test-Path $docs){Remove-Item $docs}
Function First-Query {
param([string[]]$queries)
$xlsObj = New-Object -ComObject Excel.Application
$xlsObj.DisplayAlerts = $false
## - Create new Workbook and Sheet (Visible = 1 / 0 not visible)
$xlsObj.Visible = 0
$xlsWb = $xlsobj.Workbooks.Add(1)
$xlsSh = $xlsWb.Worksheets.Add([System.Reflection.Missing]::Value, $xlsWb.Worksheets.Item($xlsWb.Worksheets.Coun))
$xlsSh.Name = 'Test'
for ($i = 0; $i -lt $queries.Count; $i++){
$query = $queries[$i]
$SQLServer = 'Server'
$Database = 'DataBase'
## - Connect to SQL Server using non-SMO class 'System.Data':
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server = $SQLServer; Database = $Database; Integrated Security = True"
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = $query
$SqlCmd.Connection = $SqlConnection
## - Extract and build the SQL data object '$DataSetTable':
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $SqlCmd;
$tables = New-Object System.Data.DataSet;
$SqlAdapter.Fill($tables)
$TableArray = #($tables)
$SqlConnection.Close()
$DataSetTable = $TableArray.Tables[0]
}#End For Loop
## - Build the Excel column heading:
[Array] $getColumnNames = $DataSetTable.Columns | Select ColumnName;
## - Build column header:
[Int] $RowHeader = 1;
foreach ($ColH in $getColumnNames){
$xlsSh.Cells.item(1, $RowHeader).font.bold = $true;
$xlsSh.Cells.item(1, $RowHeader) = $ColH.ColumnName;
$RowHeader++;
}
## - Adding the data start in row 2 column 1:
[Int] $rowData = 2;
[Int] $colData = 1;
foreach ($rec in $DataSetTable.Rows){
foreach ($Coln in $getColumnNames){
## - Next line convert cell to be text only:
$xlsSh.Cells.NumberFormat = "#";
## - Populating columns:
$xlsSh.Cells.Item($rowData, $colData) = `
$rec.$($Coln.ColumnName).ToString()
$ColData++
}
$rowData++; $ColData = 1
}
## - Adjusting columns in the Excel sheet:
$xlsRng = $xlsSH.usedRange
$xlsRng.EntireColumn.AutoFit() | Out-Null
#End for loop.
#Delete unwanted Sheet1.
$xlsWb.Sheets.Item('Sheet1').Delete()
#Set Monday to Active Sheet upon opening Workbook.
$xlsWb.Sheets.Item('Monday').Activate()
## ---------- Saving file and Terminating Excel Application ---------- ##
$xlsFile = "C:\Temp\SQL\test.xlsx"
$xlsObj.ActiveWorkbook.SaveAs($xlsFile) | Out-Null
$xlsObj.Quit()
## - End of Script - ##
start-sleep 2
While ([System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsRng)) {'cleanup xlsRng'}
While ([System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsSh)) {'cleanup xlsSh'}
While ([System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsWb)) {'cleanup xlsWb'}
While ([System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsObj)) {'cleanup xlsObj'}
[gc]::collect() | Out-Null
[gc]::WaitForPendingFinalizers() | Out-Null
}#End Function
$queries = #()
$queries += #'
SELECT DISTINCT
'#
First-Query -queries $queries
Not sure I really understand what your problem is but below is a "template" that might help you to do what you want. It shows you how you can create sheets and handle them. You'll have to fill the blanks (see the commented section, where you have to call your query function).
param (
[string] $ExcelFile = (Read-Host "Enter full path for Excel file")
)
try
{
$Error.Clear()
# http://support.microsoft.com/kb/320369
[System.Threading.Thread]::CurrentThread.CurrentCulture = [System.Globalization.CultureInfo] "en-US"
Push-Location
$scriptPath = Split-Path -parent $MyInvocation.MyCommand.Path
Set-Location $scriptPath
$Excel = New-Object -comobject Excel.Application
$Excel.Visible = $True
$WorksheetCount = 7
$Workbook = $Excel.Workbooks.Add()
$Workbook.Title = 'My Workbook'
$weekdays = #("Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday")
($WorksheetCount - 1)..0 | %{
$sheet = $Excel.Worksheets.Add()
$sheet.Name = $weekdays[$_]
# $dataTable = Execute-Your-Query-Here-Returning-A-Data-Table
# $x = 0
# $dataTable | %{
# $sheet.cells.item($x, 1) = ...
# $sheet.cells.item($x, 2) = ...
# $x++
# }
}
$excel.ActiveWorkbook.SaveAs("$ExcelFile")
}
catch
{
"$($MyInvocation.InvocationName): $Error"
}
finally
{
Pop-Location
$Excel.Quit()
$Excel = $null
[gc]::collect()
[gc]::WaitForPendingFinalizers()
}

Resources