Thursday, December 12, 2019

Reading registry values in powershell without an error red lettering


Surprisingly there wasn't a complete answer for this when searching the web after a coworker asked me for help with this. I found some examples but they still would red letter when the key wasn't present and that's not something you want an end user to see and be confused about. There's obviously a difference between the script having a failure error and not being able to find a registry key value because it happens to not be present or they have a different version of Office for example, so maybe the 15.0 key is present, but the 16.0 is not.

The below should remove the red lettering and allow you to enumerate through the key values if they are present and the path is present. Enjoy.



 
Function Get-RegistryKeyPropertiesAndValues
{
    Param(
    [Parameter(Mandatory=$true)]
    [string]$path)

    if (test-path $path)
    {
        Set-Location -Path $path
        Get-Item . |

        Select-Object -ExpandProperty property |
        ForEach-Object {
            New-Object psobject -Property @{“property”=$_;
            “Value” = (Get-ItemProperty -Path . -Name $_).$_}
        }
    }
    else
    {
        return "path doesn't exist";
    }
}

cls;

$path = "HKCU:\Software\Microsoft\Office\16.0\Outlook\AutoDiscover";
#$path = "HKLM:\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell";
$valz = Get-RegistryKeyPropertiesAndValues -path $path;

if ($valz -ne "path doesn't exist")
{
    foreach ($val in $valz)
    {
        Write-Host "property: " $val.property;
        Write-Host "value: " $val.Value;
    }
}
else
{
    $valz;
}

Monday, October 28, 2019

Getting or setting values in O365 with a Powershell function



Pay special attention to the values $ConnectionUri, $AzureADAuthorizationEndpointUri and $ModulePath as you may need to change them. My c:\exonline folder is actually a duplicate of the folder found on my machine (after the O365 install process) C:\Users\[sam account name]\AppData\Local\Apps\2.0\998QKRDT.T3O\KL00A4OZ.N0K\micr...exe_1975b8453054a2b5_0010.0000_none_1e2f2accd43128c3 . I don't know if this is a static path or not, aside from the profile name. You can also try this $Path = "$Env:LOCALAPPDATA\Apps\2.0\*\CreateExoPSSession.ps1"

This function uses c# to grab the email address for auth with o365, so that other Powershell modules and code don't have to be loaded before logging in. If you use something other than pass through ticket auth, this function won't work out of the box and you'll have to add creds to your new-pssession. It will open and close the session for each command sent. This can be optimized if you need to loop through a bunch of things in each session, I did not so I did not add that complication.

Try it!


function getExoCommandReturnValue
{
[CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true, Position=0)]
        [string]$commandString,

[Parameter(Mandatory=$false, Position=1)]
        [string]$argumentList
    )

$mailAddr =
@'
using System;
using System.Data;
using System.DirectoryServices;
using System.Collections;

namespace mailNS
{
public class mailCl
{
public string getMailProps()
{
    string domainName = System.DirectoryServices.AccountManagement.UserPrincipal.Current.EmailAddress;

return domainName;
}
}
}

'@

try
{
$mailAddrGet = New-Object mailNS.mailCl;
}
catch
{
$assemblies = ("System", "System.Data", "System.DirectoryServices", "System.DirectoryServices.AccountManagement", "System.Security.Principal");

Add-Type -TypeDefinition $mailAddr -ReferencedAssemblies $assemblies -Language CSharp
$mailAddrGet = New-Object mailNS.mailCl;
}

Get-PSSession | where {$_.ComputerName -eq "outlook.office365.com"} | Remove-PSSession
[string] $ConnectionUri = "https://outlook.office365.com/PowerShell"; # may need different value
[string] $AzureADAuthorizationEndpointUri = 'https://login.windows.net/common'; # may need different value

[System.Management.Automation.Remoting.PSSessionOption] $PSSessionOption = $null;
$mailProperty = $mailAddrGet.getMailProps();
$ExoPowershellModule = "Microsoft.Exchange.Management.ExoPowershellModule.dll";
$ModulePath = "c:\exonline\Microsoft.Exchange.Management.ExoPowershellModule.dll"; # this will have to be customized to the file path these files are housed

Import-Module $ModulePath -WarningAction SilentlyContinue -WarningVariable $war1 -DisableNameChecking;

$PSSession = New-ExoPSSession -ConnectionUri $ConnectionUri -AzureADAuthorizationEndpointUri $AzureADAuthorizationEndpointUri -UserPrincipalName $mailProperty -WarningAction SilentlyContinue -WarningVariable $war2 -Confirm:$false -ErrorAction SilentlyContinue -ErrorVariable $err2 -PipelineVariable $pip2 -OutVariable $out2b;

Import-PSSession $PSSession -WarningAction SilentlyContinue -DisableNameChecking -AllowClobber;
$scriptBlock = [scriptblock]::Create($commandString);
$returnValue = Invoke-Command -ScriptBlock $scriptBlock;

return $returnValue;
}

cls;

$user = "exotestuser";

$properties = getExoCommandReturnValue -commandString 'get-mailbox $user';
$properties | fl Litigation*, *quotadefaults* , retentioncomment ;

$properties = getExoCommandReturnValue -commandString 'Set-Mailbox $user -litigationholdowner "lit_hold_owner" -retentioncomment "reten_comment"';

$properties = getExoCommandReturnValue -commandString 'get-mailbox $user';
$properties | fl LitigationHold*, *quotadefaults*, retentioncomment ;



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