Thursday, September 26, 2019

Split a string based on a delimiter character, only if the character is not inside of a set of quotes in Powershell

Below is the function and example. You can also strip the code out of the function and run it inline if you assign values to the 2 variables of $lineToSplit  and $splittingCharacter . $string.Split("$char") works great if there are no quotes where a character needs to be preserved. An example of where this would be useful would be

$lineToSplit = 'home, work, "last, first", mobile, address'; Obviously you don't want to split where the comma is inside of quotes.


$valueSplit = splitOutsideOfQuotes -lineToSplit $valueSplit -splittingCharacter ",";
function splitOutsideOfQuotes
{
[CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true, Position=0)]
        [string]$lineToSplit,
        [Parameter(Mandatory=$true, Position=1)]
        [string]$splittingCharacter
    )
#Region Initialization
$charArray = $lineToSplit.ToCharArray();
$stringTemp = "";
[bool] $quoteBlockerFound = $false
$returnSplitArray = New-Object System.Collections.ArrayList;
[int] $index = 0;
[int] $charCount = $charArray.Count;
#EndRegion /Initialization
foreach ($char in $charArray)
{
$index++;
if ($char -ne $splittingCharacter)
{
if ($char -ne " ") # not equal a space
{
if ($char -ne "`"") # not equal a quote
{
$stringTemp += $char;
}
elseif (($char -eq "`"") -and ($quoteBlockerFound -eq $true)) # equals a quote
{
$quoteBlockerFound = $false;
}
else
{
$quoteBlockerFound = $true;
}
}
elseif (($char -eq " ") -and ($quoteBlockerFound -eq $true))
{
$stringTemp += $char;
}
}
elseif (($char -eq $splittingCharacter) -and ($quoteBlockerFound -eq $true))
{
$stringTemp += $char;
}
else
{
$returnSplitArray.Add($stringTemp) | Out-Null;
$stringTemp = "";
}
if ($index -eq $charCount)
{
$returnSplitArray.Add($stringTemp) | Out-Null;
$stringTemp = "";
}
}
return $returnSplitArray;
}

Wednesday, August 14, 2019

How to read an XLSX file in .net code natively, without any 3rd party tools


Much to my surprise, there is no native way to read xlsx files in C# or PowerShell. Recently I had to write code that grabbed values from an xlsx file on SharePoint and then include the values in an email that is programmatically sent out. I couldn't use 3rd party software, so this presented a challenge.

As it turns out, an xlsx file is actually a compressed file and it can be renamed to a .zip file and then the contents (mostly xmls) extracted and mapped to each other.

The code below will download an xlsx file to a local directory or open it from an local directory if the download is not needed and then it extracts the files inside and maps the xmls. I put comments at the top of the code, you need PowerShell 5.0 or later to leverage the extraction function. It also assumes the first sheet in the spreadsheet is named worksheet1, you'll have to pull that programmatically if that is not the case or change the hard coded name. The code maps the xml that carries a list of shared strings (to save space) to the worksheet xml that has a single numerical representation of that shared string inside of it, as well as a row and column delineator. Dates are tricky as Microsoft uses the number of days from January 1st, 1900 to the value as the actual numerical representation, as well as some formatting data in the XML, but if you are like me you only care about the value itself. There is some caveat where MS used 1900 as a leap year, even though it was not, google that if you are off by a day or two.

If you have more than the standard A to Z columns in use, you'll have to write extra code for that and some of the xml symbolic representations I didn't take the time to figure out what they were for as I didn't need to. If someone does, I'd appreciate a comment here explaining what they found.

If you use this code, all of yoru values should easily be accessible via $columnListOfTuples or $rowListOfTuples, which will include a row and column value, as well as the actual cell value.

This was a PITA so I hope this helps others.


<#

    $columnListOfTuples and $rowListOfTuples have everything mapped

    Each xlsx file is a compressed group of xml files into a zip file that is renamed .xlsx
    There is an xml of shared strings that are then represented by a single numerical value to save space
    There is an xml for each sheet in the spread sheet that has numerical references to the values in the shared strings xml
    To produce a virtual/programmatic representation of the spreadsheet, the code will read the sheet, then read the numerical reference than then cross reference that to the shared strings value
    I have no mapped out all functions and xmls in the compressed file, but this is enough to get you started
    It's only configured for columns A - Z, you'll have to write code for additional columsn
    You need powershell 5.0 or later to have the native unzip feature
    It assumes your first worksheet is named worksheet1
#>

cls;

#Region Read Xlsx file

    #Region Create temporary zip file and location

    $fileDate = [DateTime]::Now.ToShortDateString();
    $fileDate = $fileDate.Replace("/", "-");
    $fileTime = [DateTime]::Now.ToShortTimeString();
    $fileTime = $fileTime.Replace(":", ".");
    $fileTime = $fileTime.Replace(" ", ".");
    $fileDateTime = $fileDate + "_" + $fileTime;

    $networkFile = "http://[xxxxx].xlsx";
    $originalFile = "c:\temp\file.xlsx"; # file must exist and location must have writable rights
    #Invoke-WebRequest -Uri $networkFile -OutFile $originalFile -UseDefaultCredentials; # this is to download a network file locally, may not be needed

    $index = $originalFile.LastIndexOf("\");
    $originalFileDirectory = $originalFile.Remove($index);
    $tempCopiedFileName = $originalFileDirectory + "\tempXlsx-" + $fileDateTime + ".zip";
    $desinationPath = $originalFileDirectory + "\xlsxDecompressed";

    #Endregion /Create temporary zip file and location

    #Region Map all of the zip/xlsx file paths

    $relsXmlFile = $desinationPath + "\_rels\.rels";
    $item1XmlRelsXmlFile = $desinationPath + "\customXml\_rels\item1.xml.rels";
    $item2XmlRelsXmlFile = $desinationPath + "\customXml\_rels\item2.xml.rels";
    $item3XmlRelsXmlFile = $desinationPath + "\customXml\_rels\item3.xml.rels";
    $item1XmlFile = $desinationPath + "\customXml\item1.xml";
    $item2XmlFile = $desinationPath + "\customXml\item2.xml";
    $item3XmlFile = $desinationPath + "\customXml\item3.xml";
    $itemProps1XmlFile = $desinationPath + "\customXml\itemProps1.xml";
    $itemProps2XmlFile = $desinationPath + "\customXml\itemProps2.xml";
    $itemProps3XmlFile = $desinationPath + "\customXml\itemProps3.xml";
    $appXmlFile = $desinationPath + "\[filename]\docProps\app.xml";
    $coreXmlFile = $desinationPath + "\[filename]\docProps\core.xml";
    $customXmlFile = $desinationPath + "\[filename]\docProps\custom.xml";
    $workbookXmlRelsXmlFile = $desinationPath + "\xl\_rels\workbook.xml.rels";
    $theme1XmlFile = $desinationPath + "\xl\theme\theme1.xml";
    $sheet1XmlRelsXmlFile = $desinationPath + "\xl\worksheets|_rels|sheet1.xml.rels";
    $workSheet1XmlFile = $desinationPath + "\xl\worksheets\sheet1.xml";
    $sharedStringsXmlFile = $desinationPath + "\xl\sharedStrings.xml";
    $stylesXmlFile = $desinationPath + "\xl\styles.xml";
    $workbookXmlFile = $desinationPath + "\xl\workbook.xml";
    $contentTypesXmlFile = $desinationPath + "\[Content_Types].xml";

    #Endregion /Map all of the zip/xlsx file paths

    Copy-Item $originalFile -Destination $tempCopiedFileName -Recurse; # copy the xlsx file as a temporary zip file
    Expand-Archive -LiteralPath $tempCopiedFileName -DestinationPath $desinationPath -Force; # unzip the file
    [xml]$fileLines = Get-Content($sharedStringsXmlFile); # read the contents of the shared strings file

    #Region Map shared strings

    $arr_SharedStrings = New-Object System.Collections.Generic.List[String];
    for ($i = 0; $i -lt $fileLines.sst.si.Count; $i++ )
    {
        $line = $fileLines.sst.si[$i];
        $tuple = [tuple]::create([int]$i, [string]("`"" + $line.InnerText + "`"") );
        $arr_SharedStrings.Add($tuple); # map the shared strings to the representational number inside of the xml files
    }

    #Endregion /Map shared strings

    [xml]$fileLines = Get-Content($workSheet1XmlFile);
    $sheetData = $fileLines.worksheet.sheetData;

    #Region Map rows
    $arr_sheet1_rows = New-Object System.Collections.ArrayList; # this will have a list/tuple of each cell that makes up a row
    for ($i = 0; $i -lt $sheetData.row.Count; $i++ )
    {
        $arr_sheet1_individualRow = New-Object System.Collections.Generic.List[String];
        $rowElements = $sheetData.row[$i].c; #grab data of all cells in that row
        foreach ($element in $rowElements)
        {
            $elementsTuple = [tuple]::create([string]$element.r, [string]$element.s, [string]$element.v)
            $arr_sheet1_individualRow.Add($elementsTuple);      
        }

        $rowTuple = [Tuple]::create([int] $i+1, $arr_sheet1_individualRow);
        $arr_sheet1_rows.Add($rowTuple) | Out-Null;
    }

    $rowListOfTuples = New-Object System.Collections.ArrayList; # has row number, then column letter, then cell value
    foreach ($rowMapped in $arr_sheet1_rows)
    {
        $cellValues = $rowMapped.Item2;
        $rowNumber = $rowMapped.Item1;
        foreach ($cellValue in $cellValues)
        {
            $cellValue = $cellValue.Remove(0, 1);
            $cellValue = $cellValue.Remove($cellValue.Length - 1, 1);
            $cellValue = $cellValue.Replace(" ", "");
            $cellValuesSplit = $cellValue.Split(",");
            $columnLetter = $cellValuesSplit[0];
            $columnLetter = $columnLetter -replace '[0-9]',''
            $cellValueOnly = $cellValuesSplit[2];

            $cellTuple = [tuple]::create([int]$rowNumber, [string]$columnLetter, [string]$cellValueOnly);
            $rowListOfTuples.Add($cellTuple) | Out-Null;
        }
    }

    #Endregion /Map shared rows

    #Region Map shared columns

    $rowCount = $arr_sheet1_rows.Count;
    $sheetDataCols = $fileLines.worksheet.cols;
    $arr_sheet1_columns = New-Object System.Collections.ArrayList; # this will have a list/tuple of each cell that makes up a column
    #$arr_sheet1_columns.Add("Place holder") | Out-Null;
    #for ($i = 0; $i -lt $sheetDataCols.col.Count; $i++ )
    for ($i = 0; $i -lt $rowCount; $i++ )
    {
        if ($arr_sheet1_columns.Count -lt $sheetDataCols.col.Count)
        {
            for ($j = 0; $j -lt $sheetDataCols.col.Count; $j++ )
            {
                switch  ($j)
                {
                    0 { $columnLetterTranslated = "A"; break; }
                    1 { $columnLetterTranslated = "B"; break; }
                    2 { $columnLetterTranslated = "C"; break; }
                    3 { $columnLetterTranslated = "D"; break; }
                    4 { $columnLetterTranslated = "E"; break; }
                    5 { $columnLetterTranslated = "F"; break; }
                    6 { $columnLetterTranslated = "G"; break; }
                    7 { $columnLetterTranslated = "H"; break; }
                    8 { $columnLetterTranslated = "I"; break; }
                    9 { $columnLetterTranslated = "J"; break; }
                    10 { $columnLetterTranslated = "K"; break; }
                    11 { $columnLetterTranslated = "L"; break; }
                    12 { $columnLetterTranslated = "M"; break; }
                    13 { $columnLetterTranslated = "N"; break; }
                    14 { $columnLetterTranslated = "O"; break; }
                    15 { $columnLetterTranslated = "P"; break; }
                    16 { $columnLetterTranslated = "Q"; break; }
                    17 { $columnLetterTranslated = "R"; break; }
                    18 { $columnLetterTranslated = "S"; break; }
                    19 { $columnLetterTranslated = "T"; break; }
                    20 { $columnLetterTranslated = "U"; break; }
                    21 { $columnLetterTranslated = "V"; break; }
                    22 { $columnLetterTranslated = "W"; break; }
                    23 { $columnLetterTranslated = "X"; break; }
                    24 { $columnLetterTranslated = "Y"; break; }
                    25 { $columnLetterTranslated = "Z"; break; }
                }
                $arr_sheet1_columns.Add($columnLetterTranslated) | Out-Null;
            }       
        }

        $rowElements = $sheetData.row[$i].c; #grab data of all cells in that row
        foreach ($element in $rowElements)
        {
            $columnLetter = $element.r -replace '[^a-zA-Z-]',''
            for ($k = 0; $k -lt $arr_sheet1_columns.Count; $k++)
            {
                $column = $arr_sheet1_columns[$k];
                if ($column.StartsWith($columnLetter))
                {
                    $columnTemp = $column.ToString() + "|" + $element.v;
                    $arr_sheet1_columns.Remove($column);
                    $arr_sheet1_columns.Insert($k, $columnTemp);
                    $stop = "here";
                }
            }
        }
    }

    $columnListOfTuples = New-Object System.Collections.ArrayList; # has column letter, than row number, then cell value
    foreach ($columnMapped in $arr_sheet1_columns)
    {
        $index = $columnMapped.IndexOf("|");
        $columnMappedLetter = $columnMapped.Remove($index);

        $columnIndividualValues = $columnMapped.Remove(0, $index);
        $columnIndividualValuesSplit = $columnIndividualValues.Split("|");

        $rowNumber = 0;
        foreach($columnIndividualValueSplit in $columnIndividualValuesSplit)
        {   
            $cellTuple = [tuple]::create([string]$columnMappedLetter, [int]$rowNumber, [string]$columnIndividualValueSplit);
            $columnListOfTuples.Add($cellTuple) | Out-Null;
            $rowNumber ++;
        }
    }

    #Endregion /Map shared columns



    # Here you have all of the data and you can parse it however you want, below is an example of that

    #Region Parse the extracted data

    $rowsForParsing = New-Object System.Collections.ArrayList;
    foreach ($arr_sheet1_row in $arr_sheet1_rows)
    {
        $rowNumber = $arr_sheet1_row.Item1;
        $cellValues = New-Object System.Collections.ArrayList; 
        $rowTemp = "";

        $indexer = 0;
        foreach ($rowValue in $arr_sheet1_row.Item2)
        {
            $valueSplit = $rowValue.Replace(" ", "");
            $valueSplit = $valueSplit.Replace("(", "");
            $valueSplit = $valueSplit.Replace(")", "");
            $valueSplit = $valueSplit.Split(",");

            $cellLocation = "";
            $cellLocation = $valueSplit[0];
            $cellSType = "";
            $cellSType = $valueSplit[1];
            $cellStringRepresentationValue = "";
            $cellStringRepresentationValue = $valueSplit[2];
            $translatedValue = "";

            if ($cellStringRepresentationValue -ne "")
            {
                switch ($cellSType)
                {
                    "1" { $translatedValue = getStringMappedValue($cellStringRepresentationValue); break;}  # represents column headers
                    "2" { $translatedValue = [datetime]::FromOADate($cellStringRepresentationValue); break;}  # number of days from January 1, 1900
                    "3" { $translatedValue = getStringMappedValue($cellStringRepresentationValue); break;}  # mapped value to strings table
                    "4" {$translatedValue = getStringMappedValue($cellStringRepresentationValue); break;}  # mapped value to strings table
                    "5" { $translatedValue = getStringMappedValue($cellStringRepresentationValue); break;}  # mapped value to strings table
                    "6" { Write-Host "#6: " $rowValue; break;}  # not sure what 6 represents yet
                    "7" { Write-Host "#7: " $rowValue; break;}  # not sure what 7 represents yet
                    "8" { Write-Host "#8: " $rowValue; break;}  # not sure what 8 represents yet
                    "9" { Write-Host "#9: " $rowValue; break;}  # not sure what 9 represents yet
                    "10" { Write-Host "#10: " $rowValue; break;}  # not sure what 10 represents yet
                    "11" { Write-Host "#11: " $rowValue; break;}  # not sure what 11 represents yet     
                }
            }

            if (($rowTemp -eq "") -and ($indexer -eq 0))
            {
                $rowTemp = "$translatedValue";
            }
            else
            {       
                $rowTemp = "$rowTemp" + "|" + "$translatedValue";
            }   

            $indexer++;
        }

        if ($rowNumber -eq 22)
        {
            $stop = "here";
        }

        $rowsForParsing.Add("$rowNumber|$rowTemp") | Out-Null;
    }

    $fileInformation = New-Object System.Collections.ArrayList;
    $topRow = $rowsForParsing[1];
    foreach ($rowParsed in $rowsForParsing)
    {
        $rowParsedSplit = $rowParsed.Split("|");    
        $date = $rowParsedSplit[1];

        $pass = $true;
        try
        {
            $dtVal = get-date $date;
        }
        catch
        {
            $pass = $false;
        }

        if ($pass -eq $true)
        {
            if ((get-date) -gt (get-date $date))
            {
                $dateApplicableRow = $rowParsed;
            }
            else
            {
                Write-Host "Top row is $topRow";
                Write-Host "This weeks row is $dateApplicableRow";
                $fileInformation.Add($topRow);
                $fileInformation.Add($dateApplicableRow);
                break;
            }
        }
    }

    #Endregion /Parse the extracted data

#EndRegion /Read Xlsx file

Thursday, August 1, 2019

How to replace all commas outside of quotation marks programmatically in Powershell


From time to time I have to parse a csv file that also has names in it, usually lastname, firstname inside of quotes.

This is obnoxious if you do not want them separated and a PITA. The code below allows you to open a file and replace them with a different delineating character programmatically, so with the new format you can then key off of that new character to import into excel as a character delimited file. For the environment I'm in, I usually use the pipe character "|" .


cls
$originalFileLocation = "c:\temp\noncompatibleo365users.txt";
$newFileLocation = "c:\temp\noncompatibleo365usersParsed2.txt";
$fileLines = Get-Content($originalFileLocation);
$replacementCharacter = "|"; # set this to the character you want to use to delineate, instead of a comma
$counter = 0;

foreach ($fileLine in $fileLines)
{
$fileLineSplit = $fileLine.Split(",");
$newLine = "";
foreach ($partOfLine in $fileLineSplit)
{if (($partOfLine -notmatch '"') -and ($quoteFoundBlocker -eq $false))
            {
                $newPartOfLine = $partOfLine.Replace(",", $replacementCharacter);
                $newLine += $newPartOfLine.Trim() + $replacementCharacter;
            }
            else
            {
                if (($partOfLine -match '"') -and ($quoteFoundBlocker -eq $false))
                {
                    $quoteFoundBlocker =  $true;
                }
                elseif (($partOfLine -match '"') -and ($quoteFoundBlocker -eq $true))
                {
                    $quoteFoundBlocker =  $false;
                }
              
                $newPartOfLine = $partOfLine.Trim();
                if ($newPartOfLine.StartsWith('"'))
                {
                    if ($newPartOfLine.EndsWith('"'))
                    {
                        $newLine += $partOfLine.Trim();
                    }
                    else
                    {
                        $newLine += $partOfLine.Trim() + ", ";
                    }
                }
                elseif ($newPartOfLine.Trim().EndsWith('"'))
                {
                    $newLine += $partOfLine.Trim();
                }
                else
                {
                    $newLine += $partOfLine.Trim() + ", ";
                }          
            }
$newLine >> $newFileLocation;
$counter++;
Write-Host "Processed line count:" $counter;
}


and a function format

$returnValue = replaceDelineatorOutsideOfQuotes($sharedString);

function replaceDelineatorOutsideOfQuotes($fileLinesToDelineate)
{
$replacementCharacter = "|";
    [bool] $quoteFoundBlocker = $false;
    $newFileLines = New-Object System.Collections.ArrayList;
    foreach ($fileLine in $fileLinesToDelineate)
    {
        $fileLineSplit = $fileLine.Split(",");
        $newLine = "";
        foreach ($partOfLine in $fileLineSplit)
        {
            if (($partOfLine -notmatch '"') -and ($quoteFoundBlocker -eq $false))
            {
                $newPartOfLine = $partOfLine.Replace(",", $replacementCharacter);
                $newLine += $newPartOfLine.Trim() + $replacementCharacter;
            }
            else
            {
                if (($partOfLine -match '"') -and ($quoteFoundBlocker -eq $false))
                {
                    $quoteFoundBlocker =  $true;
                }
                elseif (($partOfLine -match '"') -and ($quoteFoundBlocker -eq $true))
                {
                    $quoteFoundBlocker =  $false;
                }
              
                $newPartOfLine = $partOfLine.Trim();
                if ($newPartOfLine.StartsWith('"'))
                {
                    if ($newPartOfLine.EndsWith('"'))
                    {
                        $newLine += $partOfLine.Trim();
                    }
                    else
                    {
                        $newLine += $partOfLine.Trim() + ", ";
                    }
                }
                elseif ($newPartOfLine.Trim().EndsWith('"'))
                {
                    $newLine += $partOfLine.Trim();
                }
                else
                {
                    $newLine += $partOfLine.Trim() + ", ";
                }          
            }
        }
      
        $newFileLines.Add($newLine) | Out-Null;
    } return $newFileLines; }

Thursday, April 25, 2019

How to get environment variables of the current logged on user, when running scheduled tasks or powershell as the System Account (NtAuthority\System)


While running a scheduled task that I needed to run as the local System account, I noticed that any environment variables were not accessible the normal method inside of Powershell, which was quite the kink. I need access to the logged on user credentials and the documents folder path and there is very little if anything available via search engines that document this, so here we are.

New-PSDrive HKU Registry HKEY_USERS;
$user = get-wmiobject -Class Win32_Computersystem | select Username;
$sid = (New-Object System.Security.Principal.NTAccount($user.UserName)).Translate([System.Security.Principal.SecurityIdentifier]).value;
$val = (Get-Item "HKU:\$sid\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders");
$myDocPath = $val.GetValue("Personal");




If you follow the instructions via this link, you can open powershell as the NTAuthority\System account to test/troubleshoot, etc.
http://powershell-guru.com/powershell-tip-53-run-powershell-as-system/

Enjoy.

Monday, April 22, 2019

Getting and setting IPMI settings through powershell

We had an issue where some servers had IMPI enabled and some did not, in order to get uniformity on > 200 servers, a quick script was developed to read and then set them all to the same value. The racadm commands documented syntax is poor at best unless you want to read through the 200+ plus page manual linked at the bottom of this. Enjoy.


#get command to read values, enable=enabled means that IPMI is enabled

cls

$servers = get-content("\\xxx\c$\Temp\Security Patches\Servers\serversComplete(E2013)-Alphabetical.txt"); # read in a list of servers

foreach ($server in $servers)
{
    $results = Invoke-Command -ComputerName $server -ScriptBlock { racadm get iDRAC.IPMILan }
    foreach ($result in $results)
    {
        if ($result.StartsWith("Enable="))
        {
            Write-Host $server ", IPMI setting is set to: " $result;
        }
    }
}


 # sets ipmi to disabled 
cls

$servers = get-content("\\xxx\c$\Temp\Security Patches\Servers\serversComplete(E2013)-Alphabetical.txt"); # read in a list of servers

foreach ($server in $servers)
{
    $results = Invoke-Command -ComputerName $server -ScriptBlock { racadm set iDRAC.IPMILan.Enable 0 } # 0 to disable, 1 to enable
       Write-Host $server;
       Write-Host $results;
       Write-Host “”;
Write-Host “”;
}


All commands



Monday, September 10, 2018

(Powershell) Faster: RedistributeActiveDatabases.ps1 –ShowDatabaseCurrentActives or dag distribution script

The output of RedistributeActiveDatabases.ps1 –ShowDatabaseCurrentActives leaves much to be desired, when you are verifying the dag distribution after failover and/or failback, in my opinion. It outputs an endless list of moves you didn't attempt and it takes a fair amount of time to give the result.

I've created a multithreaded powershell script that does the same thing in about 1/3 of the time of the default MS script. You can either use the script name in powershell with the parameter -dagName or you're going to get a pop up box asking for the dag name. The way that it works is that it leverages the add-type cmdlet in powershell to create an object using c# code, which uses multithreading to return an object to the powershell code, which then parses that object and displays the results.


You'll have to insert your Exchange server or load balancing url you use with your domain, but that's all you should have to change unless there is authentication settings specific to your environment. If you don't use kerberos, you'll have to change that part of the code to negotiate or whatever suits your environment. If it's basic you'll need to include explicit credentials, an example of that is shown at the bottom of this blog. If you aren't familiar with c#, implementing that could be confusing to you. Another hangup could be if you use http or https for your connection to your Exchange server or load balanced url. You'll need to run it on a server to ensure it has access to all of the dlls that need to be imported.

All of that being said, hopefully you don't have to make any changes aside from the url name and it works right out of the box for you. Enjoy!

<# CHANGELOG

9/10/18 - added a load balanced url

#>
<# Notes
You will have to alter the exchangeUri variable for your environments
If you have any issues with this, you may have to alter the kerberos authentication information
wmc.AuthenticationMechanism = AuthenticationMechanism.Kerberos; may need to be .Negotiate and/or you may need to connect
directly to a server with the exchangeUri via http or https, instead of a load balanced url
Another hurdle will occur if you don't have the proper assmemblies on the machine and loaded into the GAC so they are 
registered, in other words this probalby won't run on your desktop
#>

<# PARAMETERS #>

param 
(
[Parameter(Mandatory=$false,Position=1)]
    [string]$dagName
#[Parameter(Mandatory=$False,Position=2)]
#[string]$emailAddressNamesList
)

<# /PARAMETERS #>


cls

$localMachineName = $env:computername;

if (($localMachineName.ToString().ToLower() -notlike "*-dag*") -and ($localMachineName.ToString().ToLower() -notlike "*-cas*"))
{
    Write-Host $localMachineName.ToString().ToLower();
$session = new-pssession -ConfigurationName Microsoft.Exchange -ConnectionUri 'http://[exchange server or load balancer url here]/powershell';
import-pssession $session -AllowClobber; 
}

set-adserversettings -viewentireforest $true;

[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic') | Out-Null;

if (($dagName -eq $null) -or ($dagName -eq ""))
{
$dagName = [Microsoft.VisualBasic.Interaction]::InputBox("Enter a dag name", "Dag");
}

$time = New-Object system.Diagnostics.Stopwatch  
$time.Start() 
$system = $time.Elapsed.TotalMilliseconds

$dagObject = Get-DatabaseAvailabilityGroup -Identity $dagName;

$servers = New-Object System.Collections.ArrayList;
foreach($server in $dagObject.Servers) # foreach($server in $dagObject.Servers){$servers.Add($server) | Out-Null;
{
$servers.Add($server) | Out-Null;
}
$servers = $servers | Sort-Object;

Write-Host "Database distribution check for" $dagName -ForegroundColor Yellow -BackgroundColor Black;
Write-Host "";

$distribution = 
@'
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Security;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Security.Principal;

namespace MultiThreading
{
public class dagDistribution
{
public string exchangeUri = "http://[exchange server or load balancer url here]/powershell";
public List<string> get(string dag)
{
DateTime start = DateTime.Now;

            var response = new ConcurrentBag<Collection<PSObject>>();
            var exceptions = new ConcurrentQueue<Exception>();

List<string> serversUnsorted = getDagMembers(dag);
            var servers = from s in serversUnsorted orderby s select s;

            try
            {
                Parallel.ForEach(servers, server =>
                {
                    response.Add(runPowerShellScript(server));
                });
            }
            catch (AggregateException ae)
            {
                foreach (var aex in ae.InnerExceptions)
                {
                    exceptions.Enqueue(aex);
                }
            }

            List<string> returnValues = new List<string>();
            foreach (var item in response)
            {
                string returnValue = parseServerResults(item);
                returnValues.Add(returnValue);
            }

            returnValues.Sort();
return returnValues;
}
public List<string> getServers(string[] serverz)
{
DateTime start = DateTime.Now;

            var response = new ConcurrentBag<Collection<PSObject>>();
            var exceptions = new ConcurrentQueue<Exception>();
string[] serversUnsorted = serverz;
            var servers = from s in serversUnsorted orderby s select s;

            try
            {
                Parallel.ForEach(servers, server =>
                {
                    response.Add(runPowerShellScript(server));
                });
            }
            catch (AggregateException ae)
            {
                foreach (var aex in ae.InnerExceptions)
                {
                    exceptions.Enqueue(aex);
                }
            }

            List<string> returnValues = new List<string>();
            foreach (var item in response)
            {
                string returnValue = parseServerResults(item);
                returnValues.Add(returnValue);
            }

            returnValues.Sort();
return returnValues;
}
    private Collection<PSObject> runPowerShellScript(object server)
        {
            Collection<PSObject> psobjs = new Collection<PSObject>();
            string result = "";
            string serverName = server.ToString();

            WSManConnectionInfo wmc = new WSManConnectionInfo(new Uri(exchangeUri));
            wmc.AuthenticationMechanism = AuthenticationMechanism.Kerberos;
            wmc.ShellUri = "http://schemas.microsoft.com/powershell/Microsoft.Exchange";

            using (Runspace runspace = RunspaceFactory.CreateRunspace(wmc))
            {
                PowerShell powershell = PowerShell.Create();
                
                if (runspace.RunspaceStateInfo.State == RunspaceState.Opened)
                {
                    // do nothing
                }
                else
                {
                    runspace.Open();
                    powershell.Runspace = runspace;
                }

                try
                {
                    PSCommand command = new PSCommand();
                    command.AddScript("get-mailboxdatabase -Server " + server + " -Status");
                    powershell.Commands = command;                    
                    psobjs = powershell.Invoke();

                    if (powershell.HadErrors == true)
                    {
                        result = "Failed - " + powershell.Streams.Error[0].ToString();
                        result = result.Replace("\"", "*");
                    }
                }
                catch (Exception ex)
                {
                    string fail = ex.Message;
                }
            }
            object serverNameO = server;
            PSObject serverNameObj = new PSObject(serverNameO);
            psobjs.Insert(0, serverNameObj);

            return psobjs;
        }

        private List<string> getDagMembers(string dagName)
        {
            Collection<PSObject> psobjs = new Collection<PSObject>();
            string result = "";
            string[] servers = null;
            List<string> serverList = new List<string>();

            WSManConnectionInfo wmc = new WSManConnectionInfo(new Uri(exchangeUri));
            wmc.AuthenticationMechanism = AuthenticationMechanism.Kerberos;
            wmc.ShellUri = "http://schemas.microsoft.com/powershell/Microsoft.Exchange";
           

            using (Runspace runspace = RunspaceFactory.CreateRunspace(wmc))
            {
                PowerShell powershell = PowerShell.Create();

                if (runspace.RunspaceStateInfo.State == RunspaceState.Opened)
                {
                    // do nothing
                }
                else
                {
                    runspace.Open();
                    powershell.Runspace = runspace;
                }

                try
                {
                    PSCommand command = new PSCommand();
                    command.AddScript("Get-DatabaseAvailabilityGroup -Identity " + dagName);
                    powershell.Commands = command;
                    psobjs = powershell.Invoke();

                    if (powershell.HadErrors == true)
                    {
                        result = "Failed - " + powershell.Streams.Error[0].ToString();
                        result = result.Replace("\"", "*");
                    }

                    PSPropertyInfo serversTemp = null;
                    foreach (PSObject psobj in psobjs)
                    {
                        serversTemp = psobj.Properties["servers"];
                        string s_servers = serversTemp.Value.ToString();
                        servers = s_servers.Split(' ');
                        foreach (string server in servers)
                        {
                            serverList.Add(server);
                        }
                    }

                }
                catch (Exception ex)
                {
                    string fail = ex.Message;
                }
            }            

            return serverList;
        }

private string parseServerResults(Collection<PSObject> serverObjs) // needs servername, totaldbs, activedbs, passivedbs, preferencecount (11,11,11,11), mounteddbs, dismounteddbs, dagname
        {
            // called independently with each server, first object is always the server name

            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            int index = 0;
            string returnValue = "";

            string serverName = "";
            int totalDbs = 0;
            int activeDbs = 0; // whichever has activation preference 1
            int passiveDbs = 0; // whichever has activation preference 2, 3 or 4       
            string activeCopyServerName = "";
            int activationPreferenceOne = 0;
            int activationPreferenceTwo = 0;
            int activationPreferenceThree = 0;
            int activationPreferenceFour = 0;
            int mountedCount = 0;
            int dismountedCount = 0;
            string dagName = "";
            string dagServerAndDatabaseName = "";

            foreach (PSObject obj in serverObjs)
            {
                if (index == 0)
                {
                    serverName = obj.ToString();
                }

                totalDbs = (serverObjs.Count - 1);

                PSMemberInfoCollection<PSPropertyInfo> props = obj.Properties;

                string currentPrimaryActivationServer = "";
                foreach (PSPropertyInfo prop in props)
                {
                    if (prop.Name == "MountedOnServer")
                    {
                        currentPrimaryActivationServer = prop.Value.ToString();
                        break;
                    }
                }
                
                List<string> propertyNames = new List<string>();
                foreach (PSPropertyInfo prop in props)
                {
                    string result = prop.Name + " | " + prop.Value;

                    if (prop.Name == "Mounted")
                    {
                        if (prop.Value.ToString() == "True")
                        {
                            if (currentPrimaryActivationServer.ToLower().StartsWith(serverName.ToLower()))
                            {
                                mountedCount++;
                            }
                        }
                        else
                        {
                            dismountedCount++;
                        }
                    }
                    else if (prop.Name == "MountedOnServer")
                    {
                        activeCopyServerName = prop.Value.ToString();
                    }
                    else if (prop.Name == "ActivationPreference")
                    {
                        string arr = prop.Value.ToString();
                        string[] vals = arr.Split(']');

                        foreach (string val in vals)
                        {
                            if (val != "")
                            {
                                string valTemp = val;
                                if (val.Contains("["))
                                {
                                    valTemp = val.Replace("[", "");
                                }

                                string[] preference = valTemp.Split(',');

                                string preferenceZero = preference[0].ToString().Trim();
                                string preferenceOne = preference[1].ToString().Trim();

                                if (preferenceZero.ToLower() == serverName.ToLower())
                                {
                                    if (preferenceOne == "1")
                                    {
                                        if (currentPrimaryActivationServer.ToLower().StartsWith(serverName.ToLower()))
                                        {
                                            activeDbs++;
                                        }
                                        else
                                        {
                                            passiveDbs++;
                                        }
                                    }
                                    else
                                    {
                                        if (!(currentPrimaryActivationServer.ToLower().StartsWith(serverName.ToLower())))
                                        {
                                            passiveDbs++;
                                        }
                                        else
                                        {
                                            activeDbs++;
                                        }
                                    }

                                    switch (preferenceOne)
                                    {
                                        case "1":
                                            activationPreferenceOne++;
                                            break;

                                        case "2":
                                            activationPreferenceTwo++;
                                            break;

                                        case "3":
                                            activationPreferenceThree++;
                                            break;

                                        case "4":
                                            activationPreferenceFour++;
                                            break;

                                        default:
                                            break;
                                    }
                                }
                            }
                        }
                    }
                    else if (prop.Name == "Server")
                    {
                        string activeCopyServerName2 = prop.Value.ToString();
                    }
                    else if (prop.Name == "MasterServerOrAvailabilityGroup")
                    {
                        dagName = prop.Value.ToString();
                    }
                    else if (prop.Name == "MailboxProvisioningAttributes")
                    {
                        dagServerAndDatabaseName = prop.Value.ToString();
                    }

                    propertyNames.Add(prop.Name.ToString()); // cumulative count of the property names
                }

                index++;
            }

            stopwatch.Stop();

            return returnValue = serverName + "|" + totalDbs + "|" + activeDbs + "|" + passiveDbs + "|" + activationPreferenceOne + "," + activationPreferenceTwo + "," +
                activationPreferenceThree + "," + activationPreferenceFour + "|" + mountedCount + "|" + dismountedCount + "|" + dagName;
        }
}
}
'@
try
{
$dagDistribution = New-Object MultiThreading.dagDistribution;
}
catch
{
Add-Type -TypeDefinition $distribution -ReferencedAssemblies System.Collections, System.ComponentModel, System.Data, System.Drawing, System.Linq, System.Management.Automation, System.Security, System.Threading.Tasks, System.Windows.Forms, System.Threading, System.Collections.Concurrent, System.Security.Principal
$dagDistribution = New-Object MultiThreading.dagDistribution;
}

# $results = $dagDistribution.get($dagName);
if ($servers.Count -gt 0)
{
$results = $dagDistribution.getServers($servers);
}
else
{
$results = $dagDistribution.get($dagName);
}

$results = $results | sort;
$outputTable = New-Object system.Data.DataTable “$TableName”;
#servername totaldbs activedbs passivedbs preferencecountlist mounteddbs dismounteddbs dagname
$col0 = New-Object system.Data.DataColumn "     ",([string]);
$col1 = New-Object system.Data.DataColumn ServerName,([string]);
$col2 = New-Object system.Data.DataColumn TotalDbs,([string]);
$col3 = New-Object system.Data.DataColumn ActiveDbs,([string]);
$col4 = New-Object system.Data.DataColumn PassiveDbs,([string]);
$col5 = New-Object system.Data.DataColumn PreferenceCountList,([string]);
$col6 = New-Object system.Data.DataColumn MountedDbs,([string]);
$col7 = New-Object system.Data.DataColumn DismountedDbs,([string]);
$col8 = New-Object system.Data.DataColumn DagName,([string]);

$outputTable.columns.add($col0);
$outputTable.columns.add($col1);
$outputTable.columns.add($col2);
$outputTable.columns.add($col3);
$outputTable.columns.add($col4);
$outputTable.columns.add($col5);
$outputTable.columns.add($col6);
$outputTable.columns.add($col7);
$outputTable.columns.add($col8);

foreach($result in $results)
{
[string]$resultSplit = $result;
$resultSplit1 = $resultSplit.Split("|"); # -split "|";
$row = $outputTable.NewRow();
$temp0 = $resultSplit1[0];
$temp1 = $resultSplit1[1];
$temp2 = $resultSplit1[2];
$temp3 = $resultSplit1[3];
$temp4 = $resultSplit1[4];
$temp5 = $resultSplit1[5];
if ($resultSplit1[6] -ne "")
{
$temp6 = $resultSplit1[6];
}
else
{
$temp6 = "0";
}
$temp7 = $resultSplit1[7];
$row.ServerName = $resultSplit1[0];
$row.TotalDbs = $resultSplit1[1];
$row.ActiveDbs = $resultSplit1[2];
$row.PassiveDbs = $resultSplit1[3];
$row.PreferenceCountList = $resultSplit1[4];
$row.MountedDbs = $resultSplit1[5];
if ($resultSplit1[6] -ne "")
{
$row.DismountedDbs = $resultSplit1[6];
}
else
{
$row.DismountedDbs = "0";
}
$row.DagName = $resultSplit1[7];
$outputTable.Rows.Add($row);
}

$outputTable | format-table -AutoSize;

Write-host "Function completed" -ForegroundColor Yellow -BackgroundColor Black;
Write-host " ";
$time.Stop();
$totalTime = $time.Elapsed.TotalMilliseconds - $system;
[float]$runTimeSeconds = [math]::Round($totalTime/1000, 2);
[float]$runTimeMinutes = [math]::Round($totalTime/60000, 2);
Write-Host "This function took " $runTimeSeconds.ToString() " seconds (or " $runTimeMinutes.ToString() " minutes) to run";


Explicit credential example


 string loginPassword = "password here";
System.Security.SecureString secpassword = new SecureString();
            foreach (char c in loginPassword)
            {
                secpassword.AppendChar(c);
            }

            PSCredential credential = new PSCredential(@"domain\samaccount", secpassword);
            WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri("https://[server name or load balancer url]/powershell"), "http://schemas.microsoft.com/powershell/Microsoft.Exchange", credential);
            connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Basic;