0

I've got a strange issue with Powershell. I've created a script that collects info from the SQL server and I need to crop it all into 1 table. I've tried using the SqlServerCmdletSnapin but on some of my servers it returns nothing. So i decided to try and go old school and use connection strings. However when I do that the Join-Object operation returns nothing. Here are shortened versions of the scripts. The first is using the Cmdlet, the second is the old way.

1st

function AddItemProperties($item, $properties, $output)
{
    if($item -ne $null)
    {
        foreach($property in $properties)
        {
            $propertyHash =$property -as [hashtable]
            if($propertyHash -ne $null)
            {
                $hashName=$propertyHash["name"] -as [string]
                if($hashName -eq $null)
                {
                    throw "there should be a string Name"  
                }

                $expression=$propertyHash["expression"] -as [scriptblock]
                if($expression -eq $null)
                {
                    throw "there should be a ScriptBlock Expression"  
                }

                $_=$item
                $expressionValue=& $expression

                $output | add-member -MemberType "NoteProperty" -Name $hashName -Value $expressionValue
            }
            else
            {
                # .psobject.Properties allows you to list the properties of any object, also known as "reflection"
                foreach($itemProperty in $item.psobject.Properties)
                {
                    if ($itemProperty.Name -like $property)
                    {
                        $output | add-member -force -MemberType "NoteProperty" -Name $itemProperty.Name -Value $itemProperty.Value
                    }
                }
            }
        }
    }
}

function WriteJoinObjectOutput($leftItem, $rightItem, $leftProperties, $rightProperties, $Type)
{
    $output = new-object psobject
    if($Type -eq "AllInRight")
    {
        # This mix of rightItem with LeftProperties and vice versa is due to
        # the switch of Left and Right arguments for AllInRight
        AddItemProperties $rightItem $leftProperties $output
        AddItemProperties $leftItem $rightProperties $output
    }
    else
    {
        AddItemProperties $leftItem $leftProperties $output
        AddItemProperties $rightItem $rightProperties $output
    }
    $output
}
<#
.Synopsis
   Joins two lists of objects
.DESCRIPTION
   Joins two lists of objects
.EXAMPLE
   Join-Object $a $b "Id" ("Name","Salary")
#>
function Join-Object
{
    [CmdletBinding()]
    [OutputType([int])]
    Param
    (
        # List to join with $Right
        [Parameter(Mandatory=$true,
                   Position=0)]
        [object[]]
        $Left,
        # List to join with $Left
        [Parameter(Mandatory=$true,
                   Position=1)]
        [object[]]
        $Right,
        # Condition in which an item in the left matches an item in the right
        # typically something like: {$args[0].Id -eq $args[1].Id}
        [Parameter(Mandatory=$true,
                   Position=2)]
        [scriptblock]
        $Where,
        # Properties from $Left we want in the output.
        # Each property can:
        # - Be a plain property name like "Name"
        # - Contain wildcards like "*"
        # - Be a hashtable like @{Name="Product Name";Expression={$_.Name}}. Name is the output property name
        #   and Expression is the property value. The same syntax is available in select-object and it is 
        #   important for join-object because joined lists could have a property with the same name
        [Parameter(Mandatory=$true,
                   Position=3)]
        [object[]]
        $LeftProperties,
        # Properties from $Right we want in the output.
        # Like LeftProperties, each can be a plain name, wildcard or hashtable. See the LeftProperties comments.
        [Parameter(Mandatory=$true,
                   Position=4)]
        [object[]]
        $RightProperties,
        # Type of join. 
        #   AllInLeft will have all elements from Left at least once in the output, and might appear more than once
        # if the where clause is true for more than one element in right, Left elements with matches in Right are 
        # preceded by elements with no matches. This is equivalent to an outer left join (or simply left join) 
        # SQL statement.
        #  AllInRight is similar to AllInLeft.
        #  OnlyIfInBoth will cause all elements from Left to be placed in the output, only if there is at least one
        # match in Right. This is equivalent to a SQL inner join (or simply join) statement.
        #  AllInBoth will have all entries in right and left in the output. Specifically, it will have all entries
        # in right with at least one match in left, followed by all entries in Right with no matches in left, 
        # followed by all entries in Left with no matches in Right.This is equivallent to a SQL full join.
        [Parameter(Mandatory=$false,
                   Position=5)]
        [ValidateSet("AllInLeft","OnlyIfInBoth","AllInBoth", "AllInRight")]
        [string]
        $Type="OnlyIfInBoth"
    )
    Begin
    {
        # a list of the matches in right for each object in left
        $leftMatchesInRight = new-object System.Collections.ArrayList
        # the count for all matches  
        $rightMatchesCount = New-Object "object[]" $Right.Count
        for($i=0;$i -lt $Right.Count;$i++)
        {
            $rightMatchesCount[$i]=0
        }
    }
    Process
    {
        if($Type -eq "AllInRight")
        {
            # for AllInRight we just switch Left and Right
            $aux = $Left
            $Left = $Right
            $Right = $aux
        }
        # go over items in $Left and produce the list of matches
        foreach($leftItem in $Left)
        {
            $leftItemMatchesInRight = new-object System.Collections.ArrayList
            $null = $leftMatchesInRight.Add($leftItemMatchesInRight)
            for($i=0; $i -lt $right.Count;$i++)
            {
                $rightItem=$right[$i]
                if($Type -eq "AllInRight")
                {
                    # For AllInRight, we want $args[0] to refer to the left and $args[1] to refer to right,
                    # but since we switched left and right, we have to switch the where arguments
                    $whereLeft = $rightItem
                    $whereRight = $leftItem
                }
                else
                {
                    $whereLeft = $leftItem
                    $whereRight = $rightItem
                }
                if(Invoke-Command -ScriptBlock $where -ArgumentList $whereLeft,$whereRight)
                {
                    $null = $leftItemMatchesInRight.Add($rightItem)
                    $rightMatchesCount[$i]++
                }

            }
        }
        # go over the list of matches and produce output
        for($i=0; $i -lt $left.Count;$i++)
        {
            $leftItemMatchesInRight=$leftMatchesInRight[$i]
            $leftItem=$left[$i]

            if($leftItemMatchesInRight.Count -eq 0)
            {
                if($Type -ne "OnlyIfInBoth")
                {
                    WriteJoinObjectOutput $leftItem  $null  $LeftProperties  $RightProperties $Type
                }
                continue
            }
            foreach($leftItemMatchInRight in $leftItemMatchesInRight)
            {
                WriteJoinObjectOutput $leftItem $leftItemMatchInRight  $LeftProperties  $RightProperties $Type
            }
        }
    }
    End
    {
        #produce final output for members of right with no matches for the AllInBoth option
        if($Type -eq "AllInBoth")
        {
            for($i=0; $i -lt $right.Count;$i++)
            {
                $rightMatchCount=$rightMatchesCount[$i]
                if($rightMatchCount -eq 0)
                {
                    $rightItem=$Right[$i]
                    WriteJoinObjectOutput $null $rightItem $LeftProperties $RightProperties $Type
                }
            }
        }
    }
}

Add-PSSnapin SqlServerCmdletSnapin100 
Add-PSSnapin SqlServerProviderSnapin100
#------------------------------------------------------------------------------------------------------------------------------------------------------
$Sizehash = $null
$Sizehash = @{}

$SizeQuery = "SELECT 
    DatabaseName = DB_NAME(database_id),
    Total_MB = CAST(SUM(size) * 8. / 1024 AS DECIMAL(8,2))
    FROM sys.master_files WITH(NOWAIT)
    WHERE database_id > 4 
    GROUP BY database_id
    order by DatabaseName"

$Sizehash = Invoke-Sqlcmd -Query $SizeQuery -ServerInstance PMMCSQL1
#------------------------------------------------------------------------------------------------------------------------------------------------------
$CThash = $null
$CThash = @{}

$CTQuery = "exec sp_msforeachdb 'IF ''?'' NOT IN (''master'',''model'',''tempdb'',''msdb'',''pubs'') 
    BEGIN
    USE ?; 
    select DB_NAME() AS CTName, value AS ClientType
    FROM fn_listextendedproperty(default, default, default, default, default, default, default) 
    where name = ''Client type'';
    END'"

$CThash = Invoke-Sqlcmd -Query $CTQuery -ServerInstance PMMCSQL1
#------------------------------------------------------------------------------------------------------------------------------------------------------ 
$JoinCT = $null
$JoinCT = Join-Object `
    -Left $Sizehash `
    -Right $CThash `
    -LeftProperties DatabaseName,Total_MB `
    -RightProperties ClientType `
    -Type AllinBoth `
    -Where {$args[0].DatabaseName -eq $args[1].CTName}
$JoinCT

2nd

> function AddItemProperties($item, $properties, $output)
{
    if($item -ne $null)
    {
        foreach($property in $properties)
        {
            $propertyHash =$property -as [hashtable]
            if($propertyHash -ne $null)
            {
                $hashName=$propertyHash["name"] -as [string]
                if($hashName -eq $null)
                {
                    throw "there should be a string Name"  
                }

                $expression=$propertyHash["expression"] -as [scriptblock]
                if($expression -eq $null)
                {
                    throw "there should be a ScriptBlock Expression"  
                }

                $_=$item
                $expressionValue=& $expression

                $output | add-member -MemberType "NoteProperty" -Name $hashName -Value $expressionValue
            }
            else
            {
                # .psobject.Properties allows you to list the properties of any object, also known as "reflection"
                foreach($itemProperty in $item.psobject.Properties)
                {
                    if ($itemProperty.Name -like $property)
                    {
                        $output | add-member -force -MemberType "NoteProperty" -Name $itemProperty.Name -Value $itemProperty.Value
                    }
                }
            }
        }
    }
}

function WriteJoinObjectOutput($leftItem, $rightItem, $leftProperties, $rightProperties, $Type)
{
    $output = new-object psobject
    if($Type -eq "AllInRight")
    {
        # This mix of rightItem with LeftProperties and vice versa is due to
        # the switch of Left and Right arguments for AllInRight
        AddItemProperties $rightItem $leftProperties $output
        AddItemProperties $leftItem $rightProperties $output
    }
    else
    {
        AddItemProperties $leftItem $leftProperties $output
        AddItemProperties $rightItem $rightProperties $output
    }
    $output
}
<#
.Synopsis
   Joins two lists of objects
.DESCRIPTION
   Joins two lists of objects
.EXAMPLE
   Join-Object $a $b "Id" ("Name","Salary")
#>
function Join-Object
{
    [CmdletBinding()]
    [OutputType([int])]
    Param
    (
        # List to join with $Right
        [Parameter(Mandatory=$true,
                   Position=0)]
        [object[]]
        $Left,
        # List to join with $Left
        [Parameter(Mandatory=$true,
                   Position=1)]
        [object[]]
        $Right,
        # Condition in which an item in the left matches an item in the right
        # typically something like: {$args[0].Id -eq $args[1].Id}
        [Parameter(Mandatory=$true,
                   Position=2)]
        [scriptblock]
        $Where,
        # Properties from $Left we want in the output.
        # Each property can:
        # - Be a plain property name like "Name"
        # - Contain wildcards like "*"
        # - Be a hashtable like @{Name="Product Name";Expression={$_.Name}}. Name is the output property name
        #   and Expression is the property value. The same syntax is available in select-object and it is 
        #   important for join-object because joined lists could have a property with the same name
        [Parameter(Mandatory=$true,
                   Position=3)]
        [object[]]
        $LeftProperties,
        # Properties from $Right we want in the output.
        # Like LeftProperties, each can be a plain name, wildcard or hashtable. See the LeftProperties comments.
        [Parameter(Mandatory=$true,
                   Position=4)]
        [object[]]
        $RightProperties,
        # Type of join. 
        #   AllInLeft will have all elements from Left at least once in the output, and might appear more than once
        # if the where clause is true for more than one element in right, Left elements with matches in Right are 
        # preceded by elements with no matches. This is equivalent to an outer left join (or simply left join) 
        # SQL statement.
        #  AllInRight is similar to AllInLeft.
        #  OnlyIfInBoth will cause all elements from Left to be placed in the output, only if there is at least one
        # match in Right. This is equivalent to a SQL inner join (or simply join) statement.
        #  AllInBoth will have all entries in right and left in the output. Specifically, it will have all entries
        # in right with at least one match in left, followed by all entries in Right with no matches in left, 
        # followed by all entries in Left with no matches in Right.This is equivallent to a SQL full join.
        [Parameter(Mandatory=$false,
                   Position=5)]
        [ValidateSet("AllInLeft","OnlyIfInBoth","AllInBoth", "AllInRight")]
        [string]
        $Type="OnlyIfInBoth"
    )
    Begin
    {
        # a list of the matches in right for each object in left
        $leftMatchesInRight = new-object System.Collections.ArrayList
        # the count for all matches  
        $rightMatchesCount = New-Object "object[]" $Right.Count
        for($i=0;$i -lt $Right.Count;$i++)
        {
            $rightMatchesCount[$i]=0
        }
    }
    Process
    {
        if($Type -eq "AllInRight")
        {
            # for AllInRight we just switch Left and Right
            $aux = $Left
            $Left = $Right
            $Right = $aux
        }
        # go over items in $Left and produce the list of matches
        foreach($leftItem in $Left)
        {
            $leftItemMatchesInRight = new-object System.Collections.ArrayList
            $null = $leftMatchesInRight.Add($leftItemMatchesInRight)
            for($i=0; $i -lt $right.Count;$i++)
            {
                $rightItem=$right[$i]
                if($Type -eq "AllInRight")
                {
                    # For AllInRight, we want $args[0] to refer to the left and $args[1] to refer to right,
                    # but since we switched left and right, we have to switch the where arguments
                    $whereLeft = $rightItem
                    $whereRight = $leftItem
                }
                else
                {
                    $whereLeft = $leftItem
                    $whereRight = $rightItem
                }
                if(Invoke-Command -ScriptBlock $where -ArgumentList $whereLeft,$whereRight)
                {
                    $null = $leftItemMatchesInRight.Add($rightItem)
                    $rightMatchesCount[$i]++
                }

            }
        }
        # go over the list of matches and produce output
        for($i=0; $i -lt $left.Count;$i++)
        {
            $leftItemMatchesInRight=$leftMatchesInRight[$i]
            $leftItem=$left[$i]

            if($leftItemMatchesInRight.Count -eq 0)
            {
                if($Type -ne "OnlyIfInBoth")
                {
                    WriteJoinObjectOutput $leftItem  $null  $LeftProperties  $RightProperties $Type
                }
                continue
            }
            foreach($leftItemMatchInRight in $leftItemMatchesInRight)
            {
                WriteJoinObjectOutput $leftItem $leftItemMatchInRight  $LeftProperties  $RightProperties $Type
            }
        }
    }
    End
    {
        #produce final output for members of right with no matches for the AllInBoth option
        if($Type -eq "AllInBoth")
        {
            for($i=0; $i -lt $right.Count;$i++)
            {
                $rightMatchCount=$rightMatchesCount[$i]
                if($rightMatchCount -eq 0)
                {
                    $rightItem=$Right[$i]
                    WriteJoinObjectOutput $null $rightItem $LeftProperties $RightProperties $Type
                }
            }
        }
    }
}
#------------------------------------------------------------------------------------------------------------------------------------------------------
$ConnTimeout = 30
$QueryTimeout = 120
$DB = "master"
$conn=New-Object System.Data.SqlClient.SQLConnection
$conn.ConnectionString = "Server=PMMCSQL1;Database=$DB;Integrated Security=True;Connect Timeout=$ConnTimeout"
$SQLcmd=New-Object system.Data.SqlClient.SqlCommand  
$SQLcmd.CommandTimeout=$QueryTimeout 
$conn.Open()
$SizeQuery = "SELECT 
    DatabaseName = DB_NAME(database_id),
    Total_MB = CAST(SUM(size) * 8. / 1024 AS DECIMAL(8,2))
    FROM sys.master_files WITH(NOWAIT)
    where database_id > 4 
    group BY database_id
    order by DatabaseName"

$SQLcmd.CommandTimeout = $QueryTimeout          
$SqlCmd.CommandText = $SizeQuery
$Sqlcmd.Connection = $conn
$SQLAdap=New-Object system.Data.SqlClient.SqlDataAdapter($SQLcmd)
$DataSet=New-Object system.Data.DataSet
$SQLAdap.fill($DataSet) | Out-Null
$conn.Close()
$Sizehash = $DataSet.Tables | Format-Table
$SQLAdap = $null
$DataSet = $null
#------------------------------------------------------------------------------------------------------------------------------------------------------
$conn.Open()    
$CTQuery = "exec sp_msforeachdb 'IF ''?'' NOT IN (''master'',''model'',''tempdb'',''msdb'',''pubs'') 
    BEGIN
        USE ?; 
        select DB_NAME() AS CTName, value AS ClientType
        FROM fn_listextendedproperty(default, default, default, default, default, default, default) 
        where name = ''Client type'';
    END'"

$SQLcmd.CommandTimeout = $QueryTimeout          
$SqlCmd.CommandText = $CTQuery
$Sqlcmd.Connection = $conn
$SQLAdap=New-Object system.Data.SqlClient.SqlDataAdapter($SQLcmd)
$DataSet=New-Object system.Data.DataSet
$SQLAdap.fill($DataSet) | Out-Null
$conn.Close()
$CThash = $DataSet.Tables | Format-Table
$SQLAdap = $null
$DataSet = $null
#------------------------------------------------------------------------------------------------------------------------------------------------------
$JoinCT = $null
$JoinCT = Join-Object `
    -Left $Sizehash `
    -Right $CThash `
    -Where {$args[0].DatabaseName -eq $args[1].CTName} `
    -LeftProperties DatabaseName,Total_MB `
    -RightProperties ClientType `
    -Type AllinBoth
    $JoinCT
David W
  • 13
  • 1
  • 4

2 Answers2

0

Why reinvent the wheel? If you are trying to join tables from different databases on different servers create a linked server and let SQL do the joins.

sql query for join two tables of different databases that are in two Servers

Community
  • 1
  • 1
Raf
  • 9,681
  • 1
  • 29
  • 41
  • That SQL query requires you to know all the servers that you plan on querying. I'm trying to create a script that will poll all servers that reside in an AD OU. It's not unusual for us to add or remove a server and I don't want to have to edit the script every time. – David W Feb 24 '14 at 16:26
0
($Sizehash | Select DatabaseName,Total_MB) | Join ($CThash | Select CTName,ClientType) -on DatabaseName -eq CTName

See: In Powershell, what's the best way to join two tables into one?

iRon
  • 20,463
  • 10
  • 53
  • 79