SqlBIManager.psm1

$Script:asServer = $null
$Script:LogVerbose = $false
$Script:LogInformation = $true
$Script:LogWarning = $true
$Script:SBMLogFilePath = $null

#region Logging
function Use-SbmLog{
    param(
        [string]$FilePath,
        [switch]$LogVerbose,
        [switch]$IgnoreInformation,
        [switch]$IgnoreWarning
    )
    $Script:LogVerbose = $LogVerbose
    $Script:LogInformation = !$IgnoreInformation
    $Script:LogWarning = !$IgnoreInformation
    $Script:SBMLogFilePath = $FilePath
}
function Write-SbmLogVerbose{
    param([string] $Message)
    Write-Verbose $Message
    if ($Script:LogVerbose) {
        if ($Script:SBMLogFilePath){
            $Message | Out-File FilePath $Script:SBMLogFilePath -Append -Force 
        }
    }
}
function Write-SbmLogInformation{
    param([string] $Message)
    Write-Information $Message
    if ($Script:LogInformation) {
        if ($Script:SBMLogFilePath){
            $Message | Out-File FilePath $Script:SBMLogFilePath -Append -Force 
        }
    }
}
function Write-SbmLogWarning{
    param([string] $Message)
    Write-Warning $Message
    if ($Script:LogWarning) {
        if ($Script:SBMLogFilePath){
            $Message | Out-File FilePath $Script:SBMLogFilePath -Append -Force 
        }
    }
}
function Write-SbmLogError{
    param([string] $Message)
    Write-Error $Message
    if ($Script:SBMLogFilePath){
        $Message | Out-File FilePath $Script:SBMLogFilePath -Append -Force 
    }
}

#endregion


#region SSAS
function Connect-ASServer{
<#
.SYNOPSIS
Connect to Analysis Services instance
.PARAMETER ASInstance
Server name or server name with instance name
.PARAMETER ConnectionOptions
Optional string added to the connection string
.EXAMPLE
Connect-ASServer MyAsServer
.EXAMPLE
Connect-ASServer mySqlSerer.Internal.MyDomain.com\TABULAR
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param(
        [parameter(Mandatory=$true)][string]$ASInstance,
        [string] $ConnectionOptions
    )
 
    if ($Script:asServer.Connected)
    {
        $Script:asServer.Disconnect()
    }
    # Create a connection to the server instance
    $connString = "Provider=MSOLAP;DataSource=" + $ASInstance + ";"  + $ConnectionOptions
    $Script:asServer = New-Object -TypeName Microsoft.AnalysisServices.Server
    $Script:asServer.Connect($connString) 

    Write-SbmLogInformation "Connected to instance $ASInstance ($($asServer.ServerMode))" 
 }

 function ConvertTo-AsmlDatabase
{
<#
    .SYNOPSIS
    Gets the meta data for a database.
 
    .DESCRIPTION
    Creates a Analysis server Meta Language database object for a database on the currently connected AS server.
 
    .PARAMETER Database
    ASDatabase object (from Get-ASDatabase). Accepts input from pipeline.
 
    .EXAMPLE
    #Exports all databases on the server
    Connect-ASServer "myServer\mySsasTabInstancer"
    Get-ASDatabase | ConvertTo-AsmlDatabase | Write-AsmlDatabase -ScriptFolder "C:\Sources\ASDatabases"
 
#>

    param(
        [parameter(ValueFromPipeline, Mandatory=$true)][object] $Database
    )

    Process {
        $jscript = [Microsoft.AnalysisServices.Tabular.JsonScripter]::ScriptCreateOrReplace($Database)
        $createorReplace = ConvertFrom-Json  $jscript
        Write-Output $createorReplace.createOrReplace.database
    }
}

 function Get-ASDatabase
 {
 <#
    .SYNOPSIS
    Get List of all databases on the currently connected Analysis Services server.
 #>

    $script:asServer.Databases
 }

 function Clear-ASDatabase
 {
<#
    .SYNOPSIS
    Clears the data in a Analysis Server database.
 
    .DESCRIPTION
    Executes a process ClearValues on the currently connected server.
 
    .PARAMETER Database
    ASDatabase object (from Get-ASDatabase). Accepts input from pipeline.
 
    .EXAMPLE
    #Clears data in all databases on the server
    Connect-ASServer "myServer\mySsasTabInstancer"
    Get-ASDatabase | Clear-ASDatabase
 
    .EXAMPLE
    #Clears data in a specified database on the server
    Connect-ASServer "myServer\mySsasTabInstancer"
    Clear-ASDatabase -Name TestDatabase
 
#>

     param(
        [parameter(ValueFromPipeline, Mandatory=$true, ParameterSetName="ByObject")][PSObject]$Database,
        [parameter(Mandatory=$true, ParameterSetName="ByName")][string] $Name
     )

     Process {
        if ($PSBoundParameters.ContainsKey("Database"))
        {
            $Name = $Database.Name
        }
        $processDatabase = [PSCustomObject]@{ "refresh"= [PSCustomObject]@{ 
        "type" = "clearValues" 
        "objects"= @([PSCustomObject]@{ "database" = $Name})
        }}
        $ScriptContent = ConvertTo-Json $processDatabase -Depth 25
        
        Write-SbmLogInformation "Refresh clearValues $($Database.name)"
        $result = $script:asServer.Execute($scriptcontent)
        if ($result.Messages.Count -gt 0)
        {
            Write-SbmLogError("Error Clear-ASDatabase $($Name). $($result.Messages[0].Description)")
        }
     }

 }

 function Publish-AsmlDatabase
{
<#
    .SYNOPSIS
    Deploy Analysis Server Meta Language database object to server.
 
    .DESCRIPTION
    Generates CreateOrReplace script from asdatabase definition and executes on currently connected server.
 
    .PARAMETER Database
    ASML database object (deserialized from asdatabase file). Accepts input from pipeline.
 
    .PARAMETER ClearValues
    Execute process clear values before altering the database. Prevents failure from inconsistent existing data. Ignored if the database doesn't already exist.
 
    .PARAMETER RetainRoleMembers
    Retains existing role members on an existing target database. Note: members are only retained if the role exists in the source database.
 
    .PARAMETER RetainDataSources
    Retains existing datasource details such as ConnectionString and ImpersonationMode on an existing target database. Note: details are only retained if the DataSource name exists in the source database.
 
    .EXAMPLE
    Get-ChildItem C:\MySources\Release1 -Filter "*.asdatabase" | Read-AsmlDatabase | Publish-ASDatabase
 
    .EXAMPLE
    Publish-ASDatabase -Database $TestDatabase -ClearValues
 
#>

     [CmdletBinding(SupportsShouldProcess = $true ,ConfirmImpact='Medium',DefaultParameterSetName = 'default')]
     param(
        [parameter(ValueFromPipeline, Mandatory=$true)][PSObject]$Database,
        [switch] $ClearValues,
        [switch] $RetainRoleMembers,
        [switch] $RetainDataSources
     )
     Begin {
        #get list of databases alreday on target server
        $dbs = Get-ASDatabase
     }


     Process {
        $replaceDatabase = $false
        #check if database already exists
        if ($dbs.Name -contains $Database.name)
        {
           $replaceDatabase = $true
        }

        if ($replaceDatabase) {
            if ($ClearValues) {
                Clear-ASDatabase -Name $Database.name
            }
            if ($RetainRoleMembers -or $RetainDataSources) {
                # get existing database meta
                $existingAs = $dbs | Where-Object Name -contains $Database.name
                $existingAsml = ConvertTo-AsmlDatabase $existingAs

                if ($RetainRoleMembers) {
                    #loop through roles
                    foreach ($existingRole in $existingAsml.model.roles) {
                        #find matching role in replacing database
                        $replacingRole = $null
                        $replacingRole = $Database.model.roles | Where-Object name -eq $existingRole.name
                        if (!$replacingRole) {
                            Write-SbmLogInformation "Role $($existingRole.name) is being dropped, members are not retained."
                        }
                        else {
                            #loop through existing role members
                            foreach ($existingRoleMember in $existingRole.members) {
                                $replaceRoleMember = $null
                                $replaceRoleMember =  $replacingRole.members | Where-Object memberId -eq $existingRoleMember.memberId
                                if ($replaceRoleMember) {
                                    Write-SbmLogVerbose "Retaining $($existingRoleMember.memberName) on role $($existingRole.name) (also in source)."
                                }
                                else {
                                    #Does the memberscollection exist?
                                    if ($replacingRole.PSObject.Properties.match("members").Count -eq 0)
                                    {
                                        $_membersColection = @([PSCustomObject]@{"memberId"=$existingRoleMember.memberId;"memberName"=$existingRoleMember.memberName})
                                        $replacingRole | Add-Member "members" $_membersColection
                                    }
                                    else
                                    {
                                        $replacingRole.Members+= [PSCustomObject]@{"memberId"=$existingRoleMember.memberId;"memberName"=$existingRoleMember.memberName}
                                    }
                                    Write-SbmLogInformation "Retaining $($existingRoleMember.memberName) on role $($existingRole.name)."
                                }
                            }
                        }
                    }
                }

                if ($RetainDataSources) {
                    #loop through data sources
                    foreach ($existingDataSource in $existingAsml.model.dataSources) {
                        #find matching DataSource in replacing database
                        $replacingDatasource = $null
                        $replacingDatasource = $Database.model.dataSources | Where-Object name -eq $existingDataSource.name
                        if (!$replacingDatasource) {
                            Write-SbmLogInformation "DataSource $($existingDataSource.name) is being dropped, details are not retained."
                        }
                        else {
                            $Database.model.dataSources[$Database.model.dataSources.IndexOf( $replacingDatasource)] = $existingDataSource.PSObject.Copy()
                            Write-SbmLogInformation "Retaining DataSource $($existingDataSource.name) details."
                        }
                    }
                }
            }

        }

        $createorReplace = [PSCustomObject]@{ "createOrReplace"= [PSCustomObject]@{ 
        "object"= [PSCustomObject]@{ "database" = $Database.name};
        "database" = $Database  
        }}

        $ScriptContent = ConvertTo-Json $createorReplace -Depth 25
        
        if ($PSCmdlet.ShouldProcess("Database $($Database.name).",  "Create or replace")){
            Write-SbmLogInformation "Create or replace $($Database.name)"
            $result = $script:asServer.Execute($scriptcontent)
            if ($result.Messages.Count -gt 0)
            {
                Write-SbmLogError("Error Clear-ASDatabase $($Name). $($result.Messages[0].Description)")
            }
        }

     }
} 

 function Read-AsmlDatabase
 {
 <#
.SYNOPSIS
Create object from file.
.DESCRIPTION
Reads json file (default extension: asdatabase) en serializes it to a (Analysis Server Meta Language) database object.
.PARAMETER File
Full path to .database file.
.EXAMPLE
$myDb = Read-AsmlDatabase -File "C:\Sources\ASTabularDatabase\MyDb.asdatabase"
.EXAMPLE
#Deploy all datases in folder to server
Get-ChildItem C:\MySources\Release1 -Filter "*.asdatabase" | Read-AsmlDatabase | Publish-ASDatabase
 #>

     param(
        [parameter(ValueFromPipelineByPropertyName,Mandatory=$true)][Alias('PSPath')][string]$File
     )

     Process {
        $ScriptPath = Convert-Path $File
        $scriptcontent = [System.IO.File]::ReadAllText($ScriptPath)

        $jsonObject = ConvertFrom-Json -InputObject $ScriptContent
        Write-Output $jsonObject
     }

}

function Write-AsmlDatabase
{
<#
.SYNOPSIS
Serialize database object to file.
.DESCRIPTION
Serializes a (Analysis Server Meta Language) database object to a JSON string and saves it to a file. The database name will be used as file name.
.PARAMETER Database
Analysis Server Meta Language database object
.PARAMETER ScriptFolder
The folder the file is written to
.PARAMETER FileExtension
The file extension. If not supplied .asdatabase is used.
.EXAMPLE
#Exports all databases on the server
Connect-ASServer "myServer\mySsasTabInstancer"
Get-ASDatabase | ConvertTo-AsmlDatabase | Write-AsmlDatabase -ScriptFolder "C:\Sources\ASDatabases"
 
#>

     param(
        [parameter(ValueFromPipeline,Mandatory=$true)][PSObject]$Database,
        [Parameter(Mandatory=$true)][string] $ScriptFolder,
        [string] $FileExtension = "asdatabase"
     )

     Process {
        $ScriptContent = ConvertTo-Json $Database -Depth 25
        
        [System.IO.File]::WriteAllText([System.IO.Path]::Combine( $ScriptFolder,$Database.name + "." + $FileExtension),$scriptcontent)

     }
} 


function Write-ASDatabaseCreateReplaceScript
{
<#
.SYNOPSIS
creates a CreateOrReplace script from a database object.
.DESCRIPTION
Serializes a (Analysis Server Meta Language) database object to a JSON string and wraps it in a createOrReplace script syntax. The script name will be used as file name.
.PARAMETER Database
Analysis Server Meta Language database object
.PARAMETER ScriptFolder
The folder the file is written to
.PARAMETER FileExtension
The file extension. If not supplied .jsona is used.
#>

     param(
        [parameter(ValueFromPipeline,Mandatory=$true)][PSObject]$Database,
        [Parameter(Mandatory=$true)][string] $ScriptFolder,
        [string] $FileExtension = "jsona"
     )

     Process {
        $createorReplace = [PSCustomObject]@{ "createOrReplace"= [PSCustomObject]@{ 
        "object"= [PSCustomObject]@{ "database" = $Database.name};
        "database" = $Database  
        }}

        $ScriptContent = ConvertTo-Json $createorReplace -Depth 25
        
        [System.IO.File]::WriteAllText([System.IO.Path]::Combine( $ScriptFolder,$Database.name + "." + $FileExtension),$scriptcontent)

     }
} 


<#
.SYNOPSIS
Change the name of an analysis services database.
.PARAMETER Database
Analysis Server Meta Language database object.
.PARAMETER Name
Name to set the database to.
.PARAMETER Expression
Script block generating the new name.
.PARAMETER ExpressionArgumentList
Optional arguments for the new name expression.
.EXAMPLE
Get-ChildItem "\\ITShare\ContosoBI\Releases\1_4_0\SSASTABULAR\Finance_DVL.asdatabase" | Read-AsmlDatabase | Set-AsmlName -Name Finance_UAT
.EXAMPLE
Get-ChildItem \\ITShare\ContosoBI\Releases\1_4_0\SSASTABULAR -Filter *.asdatabase | Read-AsmlDatabase | Set-AsmlName -Expression {$input.name.Replace("_DVL", "_UAT")}
#>

function Set-AsmlName
{
     [CmdletBinding(SupportsShouldProcess = $true ,ConfirmImpact='Medium',DefaultParameterSetName = 'default')]
     param(
        [parameter(ValueFromPipeline,Mandatory=$true)][PSObject]$Database,
        [string] $Name,
        [ScriptBlock] $Expression,
        [Object[]] $ExpressionArgumentList
   )

     Process {
        if ($PSBoundParameters.ContainsKey("Expression")) {
            $Name = Invoke-Command -ScriptBlock $Expression -ArgumentList $ExpressionArgumentList -InputObject $Database
        }
        if ($Name) {
            if ($PSCmdlet.ShouldProcess("Change database name $($Database.name) to $($Name).")){
                $Database.name = $Name
            }
        }
        Write-Output $Database
     }
}


<#
.SYNOPSIS
Edit data source details on an analysis services database.
.PARAMETER Database
Analysis Server Meta Language database object.
.PARAMETER Name
Name of the data source in the database.
.PARAMETER ConnectionString
New connection string for the data source.
.PARAMETER ImpersonationMode
New ImpersonationMode for the data source.
#>

function Set-AsmlDatasource
{
     [CmdletBinding(SupportsShouldProcess = $true ,ConfirmImpact='Medium',DefaultParameterSetName = 'default')]
     param(
        [parameter(ValueFromPipeline,Mandatory=$true)][PSObject]$Database,
        [string] $Name,
        [string] $ConnectionString,
        [string] $ImpersonationMode
     )

     Process {
        $datasource = $Database.model.dataSources | Where-Object name -eq $Name

        if (!$datasource)
        {
            Write-SbmLogVerbose "Datasource $($Name) not found."
        }
        else
        {
            if ($PSCmdlet.ShouldProcess("Alter Datasource $($Name).")){
                Write-SbmLogInformation "Altering Datasource $($Name)."
                if ($PSBoundParameters.ContainsKey('ConnectionString')) {
                       $datasource.connectionString = $ConnectionString
                }
                if ($PSBoundParameters.ContainsKey('ImpersonationMode')) {
                    $datasource.impersonationMode=$ImpersonationMode
                }
            }
        }
        Write-Output $Database
     }
} 

<#
.SYNOPSIS
Add or Replace a member in an Analysis Services database role.
.DESCRIPTION
Add a new User or Group to a role, or replace an existing one.
.PARAMETER Database
Analysis Server Meta Language database object.
.PARAMETER RoleName
Analysis Services database role name
.PARAMETER Member
AsmlRoleMember object containing AD user or Group SID and name.
.PARAMETER ReplaceMember
Optional AsmlRoleMember object containing AD user or Group SID and name for the member to replace.
.PARAMETER Filter
Optional Filter expression evaluated for every database/role. If filter evaluates to false, member is not added.
.PARAMETER FilterArgumentList
Optional arguments evaluated in the filter.
#>

function Add-AsmlRoleMember
{
     param(
        [parameter(ValueFromPipeline,Mandatory=$true)][PSObject]$Database,
        [Parameter(Mandatory=$false)][string] $RoleName,
        [Parameter(Mandatory=$true)][PSObject] $Member,
        [PSObject] $ReplaceMember,
        [ScriptBlock] $Filter,
        [Object[]] $FilterArgumentList
     )

     Process {
        $roles = $Database.model.roles
        if ($PSBoundParameters.ContainsKey("RoleName")) {
            $roles = $roles | Where-Object name -eq $RoleName
        }
        Foreach ($role in $roles)
        {
            $filterResult = $true
            if ($PSBoundParameters.ContainsKey("Filter")) {
                $filterInput = [PSCustomObject] @{
                    "Database"=$Database
                    "Role"=$role
                }
                $filterResult = Invoke-Command -ScriptBlock $Filter -ArgumentList $FilterArgumentList -InputObject $filterInput
            }
            if ($filterResult -eq $true) {
                if ($PSBoundParameters.ContainsKey("ReplaceMember")) {
                    if($role.members.memberId -contains $ReplaceMember.memberId) {
                        Write-SbmLogInformation "Replacing member with $($Member.memberName) in role $($role.name) (Database $($Database.name))."
                        $crm = $role.members | Where-Object memberId -eq $ReplaceMember.memberId
                        $crm.memberId = $Member.memberId
                        $crm.memberName = $Member.memberName
                    }
                }
                else {
                    Write-SbmLogInformation "Adding member $($Member.memberName) to role $($role.name) (Database $($Database.name))."

                    #Does the memberscollection exist?
                    if ($role.PSObject.Properties.match("members").Count -eq 0)
                    {
                        $_membersColection = @($Member)
                        $role | Add-Member "members" $_membersColection
                    }
                    else
                    {
                        $role.Members+=$Member
                    }
                }

            }
        }
        Write-Output $Database
     }
} 


<#
.SYNOPSIS
Add or Replace a member in an Analysis Services database role.
.DESCRIPTION
Add a new User or Group to a role, or replace an existing one.
.PARAMETER Database
Analysis Server Meta Language database object.
.PARAMETER RoleName
Analysis Services database role name
.PARAMETER Member
AsmlRoleMember object containing AD user or Group SID and name.
.PARAMETER Filter
Optional Filter expression evaluated for every database/role. If filter evaluates to false, member is not removed. Tip: filter on $input.Role.name, $input.Member.memberName, $input.Database.name.
.PARAMETER FilterArgumentList
Optional arguments evaluated in the filter.
#>

function Clear-AsmlRoleMember
{
     param(
        [parameter(ValueFromPipeline,Mandatory=$true)][PSObject]$Database,
        [Parameter(Mandatory=$false)][string] $RoleName,
        [Parameter(Mandatory=$false)][PSObject] $Member,
        [ScriptBlock] $Filter,
        [Object[]] $FilterArgumentList
     )

     Process {
        $roles = $Database.model.roles
        if ($PSBoundParameters.ContainsKey("RoleName")) {
            $roles = $roles | Where-Object name -eq $RoleName
        }
        Foreach ($role in $roles)
        {
            $replacedMembers = @()
            foreach ($roleMember in $role.members) {
                $filterResult = $true
                if ($PSBoundParameters.ContainsKey("Filter")) {
                    $filterInput = [PSCustomObject] @{
                        "Database"=$Database
                        "Role"=$role
                        "Member"=$roleMember
                    }
                    $filterResult = Invoke-Command -ScriptBlock $Filter -ArgumentList $FilterArgumentList -InputObject $filterInput
                }

                if ($filterResult -eq $true) {
                    if  ($PSBoundParameters.ContainsKey("Member")) {
                        if ($roleMember.memberId -ne $Member.memberId) {
                             $filterResult = $false
                        }
                    }
                }

                if ($filterResult -eq $true) {
                    Write-SbmLogInformation "Remove member $($roleMember.memberName) from role $($role.name) (Database $($Database.name))."
                }
                else {
                    $replacedMembers += $roleMember
                }
            }
            if ($role.PSObject.Properties.Match("members").Count -gt 0){ 
                $role.members = $replacedMembers
            }
        }
        Write-Output $Database
     }
} 



#endregion

#region SSAS ActiveDirectory
function ConvertTo-AsmlRoleMember
<#
.SYNOPSIS
 Create AsmlRoleMember object from ActiveDirectory group or user
.PARAMETER ADObject
ActiveDirectory User or Group object
.PARAMETER Domain
Domain as in Domanin\AccountName
#>

{
    param(
        [parameter(Mandatory=$true,ValueFromPipeline)][object] $ADObject,
        [Parameter(Mandatory=$true)][string] $Domain
    )

    Process {
        $rm = [PSCustomObject]@{
            "memberId"=$ADObject.SID.value
            "memberName"= "$($Domain)\$($ADObject.SamAccountName)"
        }
        if ($PSCmdlet.MyInvocation.ExpectingInput) {
            Write-Output $rm
        }

    }
   
    End {
        if (!$PSCmdlet.MyInvocation.ExpectingInput) {
            $rm
        }
    }
}

function Select-AsmlRoleMember
{
<#
.SYNOPSIS
Get ActiveDirectory group or user and create AsmlRoleMember object
.PARAMETER Domain
Domain as in Domanin\AccountName
.PARAMETER ADGroup
ActiveDirectory Group Identity
.PARAMETER ADUser
ActiveDirectory User Identity
#>

    param(
        [Parameter(Mandatory=$true, ParameterSetName="ADGroup")] [Parameter(Mandatory=$true, ParameterSetName="ADUser")][string] $Domain,
        [Parameter(Mandatory=$true, ParameterSetName="ADGroup")][string] $ADGroup,
        [Parameter(Mandatory=$true, ParameterSetName="ADUser")][string] $ADUser
    )
    $adinfo = $null
    if ($PSBoundParameters.ContainsKey("ADGroup")) {
        $adinfo = Get-ADGroup -Identity $ADGroup 
    }
    else {
        $adinfo = Get-ADUser -Identity $ADUser
    }
    ConvertTo-ASMLRoleMember -ADObject $adinfo -Domain $Domain
}


#endregion

#region SSIS Catalog, Folder, Project
function Connect-SsisCatalog{
<#
.SYNOPSIS
Connect to the SSIS catalog
.PARAMETER Server
The SQL Server instance that contains the catalog database.
.PARAMETER Catalog
The SSIS catalog database name. if omitted, SSISDB is assumed
.PARAMETER Authentication
DefaultValue: NotSpecified (Windows authentication). Other values: ActiveDirectoryPassword (Azure AD UserId/Password), SqlPassword
.PARAMETER UserID
User ID for Azure AD or SQL authentication
.PARAMETER Password
Password as securestring for Azure AD or SQL authentication
.EXAMPLE
Connect-SsisCatalog MyServerName\MySqlInstance
.EXAMPLE
Connect-SsisCatalog ContosoCloudDB.database.windows.net -Catalog SSISDB -Authentication ActiveDirectoryPassword -UserID John.Doe@contoso.onmicrosoft.com -Password (ConvertTo-SecureString "***" -AsPlainText -Force)
#>

param(
    [Parameter(Mandatory=$true)][string]$Server,
    [string]$Catalog="SSISDB",
    [string]$Authentication="NotSpecified",
    [string]$UserId,
    [System.Security.SecureString]$Password
)
    # Create a connection to the server
    $SqlConnectionstring = "Data Source=$($Server);Initial Catalog=$($Catalog)" 
    if ($Authentication -eq "NotSpecified") {
        $SqlConnectionstring+= ";Integrated Security=SSPI"
    }
    elseif  ($Authentication -eq "ActiveDirectoryPassword") {
        $SqlConnectionstring+= ";Authentication=Active Directory Password"
    }
    elseif  ($Authentication -eq "SqlPassword") {
        $SqlConnectionstring+= ";Authentication=Sql Password"
    }
    else {
        Write-SbmLogError "Error unsupported Authentication $($Authentication)"
        return
    }
    $script:isSqlConnection = New-Object System.Data.SqlClient.SqlConnection $SqlConnectionstring
    if (($Authentication -eq "ActiveDirectoryPassword") -or ($Authentication -eq "SqlPassword")) {
        if (!$Password.IsReadOnly()) {
            $Password.MakeReadOnly()
        }
        $cred = New-Object System.Data.SqlClient.SqlCredential -ArgumentList $UserId,$Password
        $script:isSqlConnection.Credential = $cred
    }
 
    try{
       $script:isSqlConnection.Open()
       $script:isCatalog = $Catalog
       $sqlStatement = "SELECT COUNT(1) FolderCount FROM [$($script:isCatalog)].[catalog].[folders]"
       $sqlCmd = New-Object System.Data.SqlClient.SqlCommand  $sqlStatement , $script:isSqlConnection
       $foldercount = $sqlCmd.ExecuteScalar()
       Write-SbmLogInformation "Connected to Catalog $($Catalog) on $($Server). Found $($foldercount) Folders."
    }
    catch{
        Write-SbmLogError "Error Connecting to $($Server), Catalog $($Catalog). Details: $($_.Exception.Message)"
        return
    }
}

function Get-SsisFolder{
<#
.SYNOPSIS
Get SSIS catalog folders
#>

     $sqlStatement = "SELECT [folder_id] FolderId, [name] [FolderName] FROM [$($script:isCatalog)].[catalog].[folders]"
     $sqlCmd = New-Object System.Data.SqlClient.SqlCommand  $sqlStatement , $script:isSqlConnection
     $sqlReader =  $sqlCmd.ExecuteReader()

     $dataTable = New-Object System.Data.DataTable
     $dataTable.Load($sqlReader)

     $dataTable
}

function Get-SsisProject{
<#
.SYNOPSIS
Get SSIS catalog projects.
.PARAMETER FolderName
Optional filter on folder name.
.PARAMETER ProjectName
Optional filter on project name.
#>

     param(
        [Parameter(Mandatory=$false)][string]$FolderName,
        [string]$ProjectName
     )
     $sqlStatement = "SELECT p.[project_id] ProjectId,p.[name] ProjectName,p.[description] ProjectDescription,p.[project_format_version] ProjectFormatversion
        ,p.[deployed_by_sid] DeployedBySid,p.[deployed_by_name] DeployedByName,p.[last_deployed_time] LastDeployed,p.[created_time] Created
        ,p.[object_version_lsn] VersionLsn,p.[validation_status] ValidationStatus,p.[last_validation_time] LastValidation
        ,fl.[folder_id] FolderId, fl.[name] FolderName
        FROM [$($script:isCatalog)].[catalog].[projects] p
        JOIN [$($script:isCatalog)].[catalog].[folders] fl ON fl.[folder_id]=p.[folder_id]
        WHERE 1=1"

     if ($PSBoundParameters.ContainsKey("FolderName"))
     {
        $sqlStatement = $sqlStatement + "AND fl.[name]='$($FolderName)'"
     }
     if ($PSBoundParameters.ContainsKey("ProjectName"))
     {
        $sqlStatement = $sqlStatement + "AND p.[name]='$($ProjectName)'"
     }
     $sqlCmd = New-Object System.Data.SqlClient.SqlCommand  $sqlStatement , $script:isSqlConnection
     $sqlReader =  $sqlCmd.ExecuteReader()

     $dataTable = New-Object System.Data.DataTable
     $dataTable.Load($sqlReader)

     $dataTable
}


function Publish-SsisProject{
<#
.SYNOPSIS
Deploy SSIS Project to SSIS Catalog.
.DESCRIPTION
Deploy ispac file to SSIS Catalog folder.
.PARAMETER FilePath
Full path of ispac file
.PARAMETER FolderName
Existing Folder name on currently connected server.
#>

     param(
        [parameter(ValueFromPipelineByPropertyName, Mandatory=$true)][Alias('PSPath')][string]$FilePath,
        [Parameter(Mandatory=$true)][string]$FolderName
     )

     Begin {
        

        $importprojectCmd = New-Object System.Data.SqlClient.SqlCommand 
        $importprojectCmd.CommandType = [System.Data.CommandType]::StoredProcedure
        $importprojectCmd.CommandText = "[$($script:isCatalog)].catalog.deploy_project"
        $importprojectCmd.Connection = $script:isSqlConnection
        $importprojectCmd.Parameters.Add("@folder_name", [System.Data.SqlDbType]::NVarChar, 128).Value = $FolderName
        $importprojectCmd.Parameters.Add("@project_name", [System.Data.SqlDbType]::NVarChar, 128) | Out-Null
        $importprojectCmd.Parameters.Add("@project_stream", [System.Data.SqlDbType]::VarBinary, -1) | Out-Null
        $importprojectCmd.Parameters.Add("@operation_id", [System.Data.SqlDbType]::BigInt).Direction = [System.Data.ParameterDirection]::InputOutput
     }

     Process {
        $fp = Convert-Path $FilePath
        $ProjectName = [System.IO.Path]::GetFileNameWithoutExtension($fp)

        #see if the project already exists
        $prevProject = Get-SsisProject -FolderName $FolderName -ProjectName $ProjectName

        # Reading ispac file as binary
        [byte[]] $IspacFile = [System.IO.File]::ReadAllBytes($fp)

        $importprojectCmd.Parameters["@project_name"].Value = $ProjectName 
        $importprojectCmd.Parameters["@project_stream"].Value = $IspacFile 
        $importprojectCmd.ExecuteNonQuery()

        if ($prevProject){
            Write-SbmLogInformation "Deployed $($ProjectName) to $($FolderName) (REPLACE)"
        }
        else {
            Write-SbmLogInformation "Deployed $($ProjectName) to $($FolderName) (ADD)"
        }
     }
}

function Export-SsisProject
{
<#
.SYNOPSIS
Export a SSIS project from the catalog to an ispac file.
.PARAMETER ProjectName
SSIS catalog project name.
.PARAMETER FolderName
SSIS catalog folder containing the project.
.PARAMETER FolderPath
Full path of folder the ispac file is saved to.
#>

    param(
        [parameter(ValueFromPipelineByPropertyName, Mandatory=$true)][string]$ProjectName, 
        [Parameter(Mandatory=$true)][string]$FolderName,
        [Parameter(Mandatory=$true)][string]$FolderPath
    )

    Begin {
        $ExportProjectCmd = New-Object System.Data.SqlClient.SqlCommand 
        $ExportProjectCmd.CommandType = [System.Data.CommandType]::StoredProcedure
        $ExportProjectCmd.CommandText = "[$($script:isCatalog)].catalog.get_project"
        $ExportProjectCmd.Connection = $script:isSqlConnection
        $ExportProjectCmd.Parameters.Add("@folder_name", [System.Data.SqlDbType]::NVarChar, 128).Value = $FolderName
        $ExportProjectCmd.Parameters.Add("@project_name", [System.Data.SqlDbType]::NVarChar, 128) | Out-Null
    }

    Process {
        $ExportProjectCmd.Parameters["@project_name"].Value = $ProjectName 
        [Byte[]]$fd= $ExportProjectCmd.ExecuteScalar()
        [System.IO.File]::WriteAllBytes([System.IO.Path]::Combine($FolderPath,$ProjectName + ".ispac") , $fd)
    }
}
#endregion

#region SSIS Parameter, Environment
function Get-SsisParameter{
<#
.SYNOPSIS
Get SSIS catalog parameter.
.PARAMETER FolderName
Optional filter on folder name.
.PARAMETER ProjectName
Optional filter on project name.
.PARAMETER ObjectName
Optional filter on object name (parameter scope).
.PARAMETER WhereValueSet
Optional filter only parameters that override the default
#>

     param(
        [Parameter(Mandatory=$false)][string]$FolderName,
        [string]$ProjectName,
        [string]$ObjectName,
        [switch]$WhereValueSet
     )
     $sqlStatement = "SELECT par.[parameter_id] ParameterId
          ,par.[project_id] ProjectId
          ,prj.[name] ProjectName
          ,par.[object_type] ObjectType
          ,par.[object_name] ObjectName
          ,par.[parameter_name] ParameterName
          ,par.[data_type] DataType
          ,par.[required] [Required]
          ,par.[sensitive][Sensitive]
          ,par.[description] [Description]
          ,par.[design_default_value] [DesignDefaultValue]
          ,par.[default_value] [DefaultValue]
          ,par.[value_type] [ValueType]
          ,par.[value_set] [ValueSet]
          ,par.[referenced_variable_name] [ReferencedVariableName]
          ,par.[validation_status] [ValidationStatus]
          ,par.[last_validation_time] [LastValidation]
          ,fl.[name] [FolderName]
          ,IIF(par.[value_set]='FALSE', par.[design_default_value]
            , IIF(par.[value_type]='R',par.[referenced_variable_name],par.[default_value])) [ParameterValue]
        FROM [$($script:isCatalog)].[catalog].[object_parameters] par
        JOIN [$($script:isCatalog)].[catalog].[projects] prj ON prj.[project_id]=par.[project_id]
        JOIN [$($script:isCatalog)].[catalog].[folders] fl ON fl.[folder_id]=prj.[folder_id]
        WHERE 1=1"

     if ($PSBoundParameters.ContainsKey("FolderName")){
        $sqlStatement = $sqlStatement + "AND fl.[name]='$($FolderName)'"
     }
     if ($PSBoundParameters.ContainsKey("ProjectName")){
        $sqlStatement = $sqlStatement + "AND prj.[name]='$($ProjectName)'"
     }
     if ($PSBoundParameters.ContainsKey("ObjectName")){
        $sqlStatement = $sqlStatement + "AND par.[object_name]='$($ObjectName)'"
     }
     if ($WhereValueSet) {
        $sqlStatement = $sqlStatement + "AND par.[value_set]=1"
     }
     $sqlCmd = New-Object System.Data.SqlClient.SqlCommand  $sqlStatement , $script:isSqlConnection
     $sqlReader =  $sqlCmd.ExecuteReader()

     $dataTable = New-Object System.Data.DataTable
     $dataTable.Load($sqlReader)

     $dataTable
}

function Set-SsisParameter {
<#
.SYNOPSIS
Set SSIS catalog parameter value.
.DESCRIPTION
Sets the value of aproject or package parameter to a environment reference value or a literal value. Can take input from Get-SsisParameter output.
.PARAMETER ObjectType
20 indicates project parameter, 30 indicates package parameter. Defaults to 20.
.PARAMETER FolderName
Catalog folder name
.PARAMETER ProjectName
Catalog project name.
.PARAMETER ParameterName
Project or package parameter name, depending on ObjectType.
.PARAMETER ParameterValue
Project or package parameter literal value or reference variable name.
.PARAMETER ObjectName
Package name, required if ObjectType is 30.
.PARAMETER ValueType
V indicates literal value, R indicates referenced environment variable. Defaults to V.
#>

    [CmdletBinding(SupportsShouldProcess = $true,ConfirmImpact='Medium',DefaultParameterSetName = 'default')]
    param(
        [parameter(ValueFromPipelineByPropertyName, Mandatory=$false)][int] $ObjectType = 20,
        [parameter(ValueFromPipelineByPropertyName, Mandatory=$true)][string] $FolderName,
        [parameter(ValueFromPipelineByPropertyName, Mandatory=$true)][string] $ProjectName,
        [parameter(ValueFromPipelineByPropertyName, Mandatory=$true)][string] $ParameterName,
        [parameter(ValueFromPipelineByPropertyName, Mandatory=$true)][object] $ParameterValue,
        [parameter(ValueFromPipelineByPropertyName, Mandatory=$false)][string] $ObjectName,
        [parameter(ValueFromPipelineByPropertyName, Mandatory=$false)][string] $ValueType = "V"
    )
    Begin {
        $sqlCmd = New-Object System.Data.SqlClient.SqlCommand 
        $sqlCmd.CommandType = [System.Data.CommandType]::StoredProcedure
        $sqlCmd.CommandText = "[$($script:isCatalog)].catalog.set_object_parameter_value"
        $sqlCmd.Connection = $script:isSqlConnection
        $sqlCmd.Parameters.Add("@object_type", [System.Data.SqlDbType]::SmallInt) | Out-Null
        $sqlCmd.Parameters.Add("@folder_name", [System.Data.SqlDbType]::NVarChar, 128)| Out-Null
        $sqlCmd.Parameters.Add("@project_name", [System.Data.SqlDbType]::NVarChar, 128) | Out-Null
        $sqlCmd.Parameters.Add("@parameter_name", [System.Data.SqlDbType]::NVarChar, 128) | Out-Null
        $sqlCmd.Parameters.Add("@parameter_value", [System.Data.SqlDbType]::Variant) | Out-Null
        $sqlCmd.Parameters.Add("@object_name", [System.Data.SqlDbType]::NVarChar, 260) | Out-Null
        $sqlCmd.Parameters.Add("@value_type", [System.Data.SqlDbType]::Char, 1) | Out-Null
    }

    Process {
        $msg = "Set-SsisParameter Folder:$($FolderName) Project:$($ProjectName) Parameter:$($ParameterName) Value:$($ParameterValue)"
        if ($PSCmdlet.ShouldProcess($msg)){
            $sqlCmd.Parameters["@object_type"].Value = $ObjectType 
            $sqlCmd.Parameters["@folder_name"].Value = $FolderName
            $sqlCmd.Parameters["@project_name"].Value = $ProjectName
            $sqlCmd.Parameters["@parameter_name"].Value = $ParameterName
            $sqlCmd.Parameters["@parameter_value"].Value = $ParameterValue
            $sqlCmd.Parameters["@object_name"].Value = $ObjectName
            $sqlCmd.Parameters["@value_type"].Value = $ValueType
            [int]$exrst= $sqlCmd.ExecuteNonQuery()
            Write-SbmLogInformation -Message "$($msg) Result:$($exrst)"
        }
    }

}

function Clear-SsisParameter {
<#
.SYNOPSIS
Clear SSIS catalog parameter value.
.DESCRIPTION
Resets the value of a project or package parameter to the design default. Can take input from Get-SsisParameter output.
.PARAMETER ObjectType
20 indicates project parameter, 30 indicates package parameter. Defaults to 20.
.PARAMETER FolderName
Catalog folder name
.PARAMETER ProjectName
Catalog project name.
.PARAMETER ParameterName
Project or package parameter name, depending on ObjectType.
.PARAMETER ObjectName
Package name, required if ObjectType is 30.
#>

    [CmdletBinding(SupportsShouldProcess = $true,ConfirmImpact='Medium',DefaultParameterSetName = 'default')]
    param(
        [parameter(ValueFromPipelineByPropertyName, Mandatory=$false)][int] $ObjectType = 20,
        [parameter(ValueFromPipelineByPropertyName, Mandatory=$true)][string] $FolderName,
        [parameter(ValueFromPipelineByPropertyName, Mandatory=$true)][string] $ProjectName,
        [parameter(ValueFromPipelineByPropertyName, Mandatory=$true)][string] $ParameterName,
        [parameter(ValueFromPipelineByPropertyName, Mandatory=$false)][string] $ObjectName
    )
    Begin {
        $sqlCmd = New-Object System.Data.SqlClient.SqlCommand 
        $sqlCmd.CommandType = [System.Data.CommandType]::StoredProcedure
        $sqlCmd.CommandText = "[$($script:isCatalog)].catalog.clear_object_parameter_value"
        $sqlCmd.Connection = $script:isSqlConnection
        $sqlCmd.Parameters.Add("@object_type", [System.Data.SqlDbType]::SmallInt) | Out-Null
        $sqlCmd.Parameters.Add("@folder_name", [System.Data.SqlDbType]::NVarChar, 128)| Out-Null
        $sqlCmd.Parameters.Add("@project_name", [System.Data.SqlDbType]::NVarChar, 128) | Out-Null
        $sqlCmd.Parameters.Add("@parameter_name", [System.Data.SqlDbType]::NVarChar, 128) | Out-Null
        $sqlCmd.Parameters.Add("@object_name", [System.Data.SqlDbType]::NVarChar, 260) | Out-Null
    }

    Process {
        $msg = "Clear-SsisParameter Folder:$($FolderName) Project:$($ProjectName) Parameter:$($ParameterName) Value:$($ParameterValue)"
        if ($PSCmdlet.ShouldProcess($msg)){
            $sqlCmd.Parameters["@object_type"].Value = $ObjectType 
            $sqlCmd.Parameters["@folder_name"].Value = $FolderName
            $sqlCmd.Parameters["@project_name"].Value = $ProjectName
            $sqlCmd.Parameters["@parameter_name"].Value = $ParameterName
            $sqlCmd.Parameters["@object_name"].Value = $ObjectName
            [int]$exrst= $sqlCmd.ExecuteNonQuery()
            Write-SbmLogInformation -Message "$($msg) Result:$($exrst)"
        }
    }

}
function Get-SsisEnvironment{
<#
.SYNOPSIS
Get SSIS environment variables.
.PARAMETER FolderName
Optional filter on folder name.
.PARAMETER EnvironmentName
Optional filter on project name.
#>

     param(
        [Parameter(Mandatory=$false)][string]$FolderName,
        [string]$EnvironmentName
     )
     $sqlStatement = "SELECT env.[environment_id] [EnvironmentId]
      ,env.[name] [EnvironmentName]
      ,env.[description] [EnvironmentDescription]
      ,env.[folder_id] [FolderId]
      ,fl.[name] [FolderName]
        FROM [$($script:isCatalog)].[catalog].[environments] env
        JOIN [$($script:isCatalog)].[catalog].[folders] fl ON fl.[folder_id]=env.[folder_id]
        WHERE 1=1"

     if ($PSBoundParameters.ContainsKey("FolderName")){
        $sqlStatement = $sqlStatement + "AND fl.[name]='$($FolderName)'"
     }
     if ($PSBoundParameters.ContainsKey("EnvironmentName")){
        $sqlStatement = $sqlStatement + "AND env.[name]='$($EnvironmentName)'"
     }

     $sqlCmd = New-Object System.Data.SqlClient.SqlCommand  $sqlStatement , $script:isSqlConnection
     $sqlReader =  $sqlCmd.ExecuteReader()

     $dataTable = New-Object System.Data.DataTable
     $dataTable.Load($sqlReader)

     $dataTable
}

function Get-SsisEnvironmentVariable{
<#
.SYNOPSIS
Get SSIS environment variables.
.PARAMETER FolderName
Optional filter on folder name.
.PARAMETER EnvironmentName
Optional filter on project name.
.PARAMETER VariableName
Optional filter on environment ariablename.
#>

     param(
        [Parameter(Mandatory=$false)][string]$FolderName,
        [string]$EnvironmentName,
        [string]$VariableName
     )
     $sqlStatement = "SELECT ev.[variable_id] [VariableId]
      ,ev.[name] [VariableName]
      ,ev.[description] [VariableDescription]
      ,ev.[type] [VariableType]
      ,ev.[sensitive][VariableSensitive]
      ,ev.[value] [VariableValue]
      ,ev.[environment_id] [EnvironmentId]
      ,env.[name] [EnvironmentName]
      ,env.[folder_id] [FolderId]
      ,fl.[name] [FolderName]
        FROM [$($script:isCatalog)].[catalog].[environment_variables] ev
        JOIN [$($script:isCatalog)].[catalog].[environments] env ON env.[environment_id]=ev.[environment_id]
        JOIN [$($script:isCatalog)].[catalog].[folders] fl ON fl.[folder_id]=env.[folder_id]
        WHERE 1=1"

     if ($PSBoundParameters.ContainsKey("FolderName")){
        $sqlStatement = $sqlStatement + "AND fl.[name]='$($FolderName)'"
     }
     if ($PSBoundParameters.ContainsKey("EnvironmentName")){
        $sqlStatement = $sqlStatement + "AND env.[name]='$($EnvironmentName)'"
     }
     if ($PSBoundParameters.ContainsKey("VariableName")){
        $sqlStatement = $sqlStatement + "AND ev.[name]='$($VariableName)'"
     }

     $sqlCmd = New-Object System.Data.SqlClient.SqlCommand  $sqlStatement , $script:isSqlConnection
     $sqlReader =  $sqlCmd.ExecuteReader()

     $dataTable = New-Object System.Data.DataTable
     $dataTable.Load($sqlReader)

     $dataTable
}

function Set-SsisEnvironmentVariableValue {
<#
.SYNOPSIS
Set SSIS catalog environment variable value.
.DESCRIPTION
Sets the value of a environment variable. Can take input from Get-SsisEnvironmentVariable output.
.PARAMETER FolderName
Catalog folder name
.PARAMETER ProjectName
Catalog project name.
.PARAMETER EnvironmentName
Environment VariableName
.PARAMETER ParameterValue
Environment variable name.
.PARAMETER VariableValue
Environment variable value.
#>

    [CmdletBinding(SupportsShouldProcess = $true,ConfirmImpact='Medium',DefaultParameterSetName = 'default')]
    param(
        [parameter(ValueFromPipelineByPropertyName, Mandatory=$true)][string] $FolderName,
        [parameter(ValueFromPipelineByPropertyName, Mandatory=$true)][string] $EnvironmentName,
        [parameter(ValueFromPipelineByPropertyName, Mandatory=$true)][string] $VariableName,
        [parameter(ValueFromPipelineByPropertyName, Mandatory=$true)][object] $VariableValue
    )
    Begin {
        $sqlCmd = New-Object System.Data.SqlClient.SqlCommand 
        $sqlCmd.CommandType = [System.Data.CommandType]::StoredProcedure
        $sqlCmd.CommandText = "[$($script:isCatalog)].catalog.set_environment_variable_value"
        $sqlCmd.Connection = $script:isSqlConnection
        $sqlCmd.Parameters.Add("@folder_name", [System.Data.SqlDbType]::NVarChar, 128)| Out-Null
        $sqlCmd.Parameters.Add("@environment_name", [System.Data.SqlDbType]::NVarChar, 128) | Out-Null
        $sqlCmd.Parameters.Add("@variable_name", [System.Data.SqlDbType]::NVarChar, 128) | Out-Null
        $sqlCmd.Parameters.Add("@value", [System.Data.SqlDbType]::Variant) | Out-Null
    }

    Process {
        $msg = "Set-SsisEnvironmentVariable Folder:$($FolderName) Environment:$($EnvironmentName) Variable:$($VariableName) Value:$($VariableValue)"
        if ($PSCmdlet.ShouldProcess($msg)){
            $sqlCmd.Parameters["@folder_name"].Value = $FolderName
            $sqlCmd.Parameters["@environment_name"].Value = $EnvironmentName
            $sqlCmd.Parameters["@variable_name"].Value = $VariableName
            $sqlCmd.Parameters["@value"].Value = $VariableValue
            [int]$exrst= $sqlCmd.ExecuteNonQuery()
            Write-SbmLogInformation -Message "$($msg) Result:$($exrst)"
        }
    }

}

#endregion