Thursday, April 3, 2025

Install Powershell 7 and VSCode

 

Download and Install VSCode from

https://code.visualstudio.com/download

Use this command to check which versions are available

winget search Microsoft.PowerShell

Use this command on Windows 10 or 11 to install the latest Powershell

winget install --id Microsoft.Powershell --source winget

To install a specific version download from here



Launch VSCode and Install the Powershell extension from Microsoft





Tuesday, December 10, 2024

SCCM Configuration Manager powershell

 

Open the SCCM console and open Powershell ISE from the top left.  Powershell ISE will open with a script containing the SCCM connection values.

Run the script to make a powershell drive connection to SCCM. 

Test the connection using an SCCM cmdlet


Tuesday, February 20, 2024

Modules management

 

Once you start working with Powershell soon or later you will have to get a grip of module version management.  Basic Microsoft.Powershell.Core module cmdlets to manage modules are

Get-Module - List the modules imported in the current session or use -ListAvailable to show ones that can be imported from the PSModulePath.

Import-Module - Adds modules to the current session.
Remove-Module - Removes modules from the current session.

The full list of Microsoft.Powershell.Core modules are listed here

https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/?view=powershell-7.4

You can get a feel for what these module commands do by following this exercise.  The below screen shot was taken on on a vanilla Windows Server 2019.

Start a fresh Powershell ISE window
Get-Module to display the current modules loaded in the session.
Get-Adapter to display the network adapter info.
Get-Module to display the current modules loaded in the session.  Notice that now NetAdapter and NetAdapter.Format.Helper appear in the list.
Remove-Module -Name NetAdapter

Get-Module this now displays the list of loaded modules but NetAdapter has been removed.





The next module we're interested in here is PowerShellGet

Let see what PowerShellGet version we have with our freshly installed Windows Server 2019:







Windows PowerShell 5.1 comes with PowerShellGet version 1.0.0.1, which doesn't include the NuGet provider. This provider is required by PowerShellGet when working with the PowerShell Gallery.  As it is, PowerShellGet version 1.0.0.1 can't really do much.  It can't even update itself with help from something else.  That something else is the NuGet provider.

This subject is also covered in 

https://learn.microsoft.com/en-us/powershell/gallery/powershellget/update-powershell-51?view=powershellget-3.x

Install-PackageProvider -Name NuGet -Force

Now that's installed upgrade the PowerShellGet

Install-Module PowerShellGet -AllowClobber -Force

Close and reopen the powershell interface.

It is recommended to set the Powershell Gallery as a trusted repository using

Set-PSRepository -Name PSGallery -InstallationPolicy Trusted

To List available module versions in the PSGallery

Find-Module -name <ModuleName> 

Tuesday, August 8, 2023

Get User Groups Recursive

 param (

    [Parameter (ValueFromPipeline=$true, Mandatory=$true)]

    [string]$username = $null

       )


$dn = (Get-ADUser $username).DistinguishedName

#Get-ADGroup -LDAPFilter ("(member:1.2.840.113556.1.4.1941:={0})" -f $dn) | where {$_.name -like "*oracle*" -or $_.name -like "*FDM*"} | select -expand Name | sort name

Get-ADGroup -LDAPFilter ("(member:1.2.840.113556.1.4.1941:={0})" -f $dn) -Properties description | select Name,description | sort name

#Get-ADGroup -LDAPFilter ("(member:1.2.840.113556.1.4.1941:={0})" -f $dn) | where {$_.name -like "PERM-D-*"} | select -expand Name | sort name

#Get-ADGroup -LDAPFilter ("(member:1.2.840.113556.1.4.1941:={0})" -f $dn) | select -expand Name | sort name

Tuesday, June 21, 2022

Remotely updating machine account group membership

 In this script, an operating system tool called klist is being called to update the machine account group memberships.  It then triggers a GPO update.  This would be useful where GPO security filtering has been limited to a group and new members have been added to the group for the GPO to apply to.

Consider adjusting the gpupdate call to restrict it machine or user as necessary.


param (
    [Parameter (ValueFromPipeline=$true, Mandatory=$true)]
    [string]$Groupname = $null
            )

$TargetMachineList=get-adgroupmember $groupname | select -expand name | sort name

foreach ($compname in $TargetMachineList)

    {
    $online = test-connection -computername $compname -Beffersize 16 -count 1 -quiet

    if ($online -like "true")
        {
        invoke-command -cn $compname -command {c:\windows\system32\klist -lh 0 -li 0x3e7 purge}
        invoke-command -cn $compname -command {c:\windows\system32\gpupdate /force}
        }

    }

Tuesday, March 8, 2022

Edit XML

 

This code locates a XML code section and modifies it.  It is based on a couple of articles:

https://gist.github.com/aadennis/1efa5015fca7b0ec23ee341d0665a067

https://superuser.com/questions/560329/how-to-modify-create-values-in-xml-files-using-powershell


The powershell is here followed by the structure of the target XML

#Checks the user config file

#Checks the AllowBatchPrinting setting is set to false and changes it to false if not.

 #Target Config File Name

$ConfigFileName = $env:LOCALAPPDATA+"\Microsoft\AppV\Client\VFS\5BC6440A-16C1-4529-B9B9-1A55D86892F5\ProgramFilesX86\Mitratech Holdings Inc\DataStoreDSX\Searching Client\HitecLabs.DataStore.SearchingClient.exe.config"

#Create XML object from file

$xml = [xml] (get-content -Raw $ConfigFileName)

#Check the item number of the target item

$names=$xml.configuration.applicationSettings.'HitecLabs.DataStore.Client.Applications.VisualDataStoreHelperLibrary.Properties.Settings'.Setting.name
$count=0
foreach ($name in $names)
    {

    if ($name -like "AllowBatchPrinting")

        {$item=$count}

    else

        {$count=$count+1}

    }

#Read current value of target value
$TargetSettingValue=$xml.configuration.applicationSettings.'HitecLabs.DataStore.Client.Applications.VisualDataStoreHelperLibrary.Properties.Settings'.Setting.item($item).value

 

#Change value if necessary.
if ($TargetSettingValue="True"){$xml.configuration.applicationSettings.'HitecLabs.DataStore.Client.Applications.VisualDataStoreHelperLibrary.Properties.Settings'.Setting.item($item).value = "False"
$xml.Save($ConfigFileName)
}

Target XML structure partial file section:

<?xml version="1.0" encoding="utf-8"?>

<!--

Configuration settings for DataStore Searching Client Application

-->

<configuration>

  <configSections>

    These sections removed

    </sectionGroup>

  </configSections>

  <applicationSettings>

    <HitecLabs.DataStore.Help.Properties.Settings>

      <setting name="DSSearchingClientHelpURL" serializeAs="String">

        <value>http://servername/DSXHelp/SearchingClient/SearchingClient.htm</value>

      </setting>

    </HitecLabs.DataStore.Help.Properties.Settings>

    <!-- Document Comparer setting for document revisions dialog -->

    <HitecLabs.DataStore.Client.Applications.VisualDataStoreHelperLibrary.Properties.Settings>

      <setting name="DocumentComparisonMethod" serializeAs="String">

        <!--

            0=Microsoft Word

            1=Aspose.Words

         -->

        <value>0</value>

      </setting>

      <setting name="MaxLogFileSizeToZipForEmailInMBs" serializeAs="String">

        <value>200</value>

      </setting>

      <setting name="AllowBatchPrinting" serializeAs="String">

        <value>False</value>

      </setting>

      <setting name="PrintBatchSize" serializeAs="String">

        <value>100</value>

      </setting>

    </HitecLabs.DataStore.Client.Applications.VisualDataStoreHelperLibrary.Properties.Settings>


Tuesday, March 9, 2021

Automatically Logoff Idle Users

Google around and you'll find various ways of automatically logging off idle users.  None of them suited my requirements so I created a task scheduler coupled to a powershell script to manage it.  The advantages of this approach are

  • Can't be intercepted by users
  • Only uses existing OS tools and scripting
  • Manages user logons individually
  • Works if Switch User is used to logon other accounts
  • Simple, quick, effective with no significant processing requirement
  • Any background management tasks such as OS updates are not affected.
The exact requirements were to logoff "idle" user logon sessions from Windows 10 computers.  The computers were open access type computers.  None of the work in the sessions would have any state to save and no background processing tasks in the session would exist.  Forcing a logoff for unused logons would not result in any lost work.  In reality this does not check for "Idle" sessions or an idle computer state.  It assumes sessions are unused and not needed and forces them to logoff.

This method also requires security event auditing to be turned on so that the events are stored in the security log.

The scheduled task is set to run as SYSTEM.  Check the tick box Run with highest privileges

The scheduled task is triggered by a custom event filter. The XML of which is thus:

<QueryList>
  <Query Id="0" Path="Security">
    <Select Path="Security">*[System[(EventID=4800 or EventID=4802)]]</Select>
  </Query>
</QueryList>

The program called is the powershell.exe with the argument -file <PathToScript>

There are no conditions.

Settings - Select "Run a new instance in parallel"  This setting enables multiple user sessions to be tracked if neccessary.

The PowerShell script is as follows:

#This script has just been triggered by a user 4800(lock) or 4802(screensaver) security log event
#get user id and event time
$trigger = get-eventlog -logname security | where {$_.InstanceId -like "4800" -or $_.InstanceId -like "4802"} | select -first 1

#Get event log time
#We'll look forward from this point later.
$trigtime = get-date -date $trigger.TimeGenerated

#Get trigger SessionId
$trigMsg = $trigger.Message
$trigIDtemp = $trigMsg.split("Session ID: ") | select -last 1
$trigID = $trigIDtemp.trim()

# Every 15 minutes check to see if the user has returned
# Do this in total 11 times (during 3 hours) if the user is still not back
# log them out.

#first wait 15 mins  wait period 1
start-sleep -s 900
#wait test
#start-sleep -s 120

#count from 0 while less that 10 causes 10 checks
$count = 0
While ($count -le 10)
    {
    #Current Time
    if ($count -eq 0)
        {$TimeSpec = $trigtime}
    else
        {$TimeSpec = $CheckTime}
           
    #Has user returned in the last 15 mins?
    $CheckEvLog = $null
    $CheckEvLog = get-eventlog -logname security -After $TimeSpec | where {$_.InstanceID -eq 4801 -or $_.InstanceId -eq 4803}
    
    #Check any return events 4801(unlock) or 4803(screensaver dismissed) for user id
    if ($CheckEvLog -notlike $null) 
        {
        #were any of the events related to the trigger sessionid ?
        foreach ($logitem in $CheckEvLog)
            {
            $logitemMsg = $logitem.Message
            $entryIDtemp = $logitemMsg.split("Session ID: ") | select -last 1
            $entryID = $entryIDtemp.trim()
        
            #if sessionIDs match then user has returned so exit
            if ($entryID -eq $trigID){exit}
            }
        }
    
    #there are no return events or they are not for current session id.
    $CheckTime = Get-Date
    
    #wait 15 mins
    start-sleep -s 900
    #wait test
    #start-sleep -s 15
       
    $Count; $Count +=1
    }

#if you're here, that user/sessionID ain't back. Time periods configured above have expired.
#Time to log them off
& logoff $trigID

Create a URI File to call

 Amazon Workspace Client can use Universal Resource Identifiers (URIs) to launch with predefined parameters such as the username and region code.

This code creates a URI file and makes a call to it.  In practice addtional code would be used to determine the SAMAccountName and RegionCode

$SAMAccountName=mysamaccountname
$RegionCode=myRegionCode

$targetpath = 'workspaces://'+$SAMAccountName+'@'+$RegionCode+'?MFACode=push'

$line1 = '[InternetShortcut]' | Out-File $URIFile
$line2 = 'URL='+$targetpath | Out-File $URIFile -Append
$line3 = 'IconIndex = 0' | Out-File $URIFile -Append
$line4 = 'IconFile=C:\Program Files (x86)\Amazon Web Services, Inc\Amazon\WorkSpaces\workspaces.exe' | Out-File $URIFile -Append

Invoke-Item $URIFile


Creating an SCCM Task Sequence variable

 

During a task sequence run, it is useful to set task sequence variables to set conditions for tasks.

If using the Microsoft Deployment Tookit (MDT) task sequence variables are set for example when a virtual machine or laptop is detected.

$SMSTSEnv.Value("IsVM") will return True for virtual machines
$SMSTSEnv.Value("IsLaptop") will return True for laptops

These items can then be queried to control if laptop orientated tasks should activate.

To set a custom task sequence variable in a powershell script first create the object then set the variable value.

$SMSTSEnv = New-Object -ComObject Microsoft.SMS.TSEnvironment -ErrorAction SilentlyContinue
$SMSTSEnv.Value("TestVar")="TestVarValue"

Task sequence task options can then be conditioned on the task sequence variable value

Thursday, February 18, 2021

Removing Windows 10 Provisioned Universal Windows Platform (UWP) (AppX) apps

 

During a first logon to Windows 10 some time is taken to setup UWP apps for the user.  During the build phase you may wish to remove some of the ones you do not need.

Microsoft.549981C3F5F10
Microsoft.BingWeather
Microsoft.DesktopAppInstaller
Microsoft.GetHelp
Microsoft.Getstarted
Microsoft.HEIFImageExtension
Microsoft.Microsoft3DViewer
Microsoft.MicrosoftEdge.Stable
Microsoft.MicrosoftOfficeHub
Microsoft.MicrosoftSolitaireCollection
Microsoft.MicrosoftStickyNotes
Microsoft.MixedReality.Portal
Microsoft.MSPaint
Microsoft.Office.OneNote
Microsoft.People
Microsoft.ScreenSketch
Microsoft.SkypeApp
Microsoft.StorePurchaseApp
Microsoft.VCLibs.140.00
Microsoft.VP9VideoExtensions
Microsoft.Wallet
Microsoft.WebMediaExtensions
Microsoft.WebpImageExtension
Microsoft.Windows.Photos
Microsoft.WindowsAlarms
Microsoft.WindowsCalculator
Microsoft.WindowsCamera
microsoft.windowscommunicationsapps
Microsoft.WindowsFeedbackHub
Microsoft.WindowsMaps
Microsoft.WindowsSoundRecorder
Microsoft.WindowsStore
Microsoft.Xbox.TCUI
Microsoft.XboxApp
Microsoft.XboxGameOverlay
Microsoft.XboxGamingOverlay
Microsoft.XboxIdentityProvider
Microsoft.XboxSpeechToTextOverlay
Microsoft.YourPhone
Microsoft.ZuneMusic
Microsoft.ZuneVideo

Run this script logged in as admin and elevated.

#get provisioned apps
$AppxProv = Get-AppxProvisionedPackage -Online

#Enter the Apps you want to keep into this array

$AppsToKeep = @("Microsoft.MicrosoftOfficeHub","Microsoft.WindowsCalculator","Microsoft.Office.OneNote","Microsoft.WindowsCamera")

#loop through them

foreach ($AppxProvItem in $AppxProv)

    {
    
    $AppxprvDN = $AppxProvItem | Select -expand DisplayName

         if ($AppxprvDN -notin $AppsToKeep)

            {
            #Get the package full name
            $AppxTargetPFN = Get-AppxPackage -Name $AppxPrvDN | Select -expand PackageFullName

            #first remove the package from the logged on session
            Remove-AppxPackage -Package $AppxTargetPFN

            #then target the provisioned Appx version
            $AppxPrvTarget = Get-AppxProvisionedPackage -Online | where{$_.DisplayName -like $AppxPrvDN}

            $AppxPrvTargetPN = $AppxPrvTarget | Select -expand PackageName

            Remove-AppxProvisionedPackage -Online -PackageName $AppxPrvTargetPN

            }

    }

Monday, February 8, 2021

Suppress screen output errors

Insert this at the top of a script to stop error information appearing in the output that you are trying to read!

$ErrorActionPreference= 'silentlycontinue'

Tuesday, January 12, 2021

Change Permissions and overwrite img0.jpg

 C:\Windows\Web\Wallpaper\Windows\img0.jpg is typically the default wallpaper for Windows 10.  As part of the OS can be tricky to change unless you adjust the permissions.  This script was designed to run in an SCCM task sequence

$ScriptsPath = Split-Path - Parent $MyInvocation.MyCommand.Path

$img0new = $ScriptsPath+"\img0.jpg
$img0 = "C:\Windows\Web\Wallpaper\Windows\img0.jpg

#modify default permissions on img0.jpg
takeown /f $img0
icacls $img0 /Grant 'System:(F)'

Copy-Item -Path $img0new -Destination $img0 -Force

Wednesday, October 21, 2020

Powershell popup


Article on how to produce pop-ups in powershell.

 https://powershell.org/2013.04/powershell-popup


$wshell = New-Object -ComObject Wscript.Shell -ErrorAction Stop
$wshell.Popup("Are you looking at me?",0,"Hey!",48+4)

Tuesday, March 10, 2020

Test-Connection has a TCPPort option in Powershell v6+


Powershell versions 6 and above provide the test-connection cmdlet with a port specification option

-TCPPort <portnumber>

https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/test-connection/?view=powershell-7


Note that ISE is not used for versions of powershell above 5.1.
https://docs.microsoft.com/en-us/powershell/scripting/components/ise/introducing-the-windows-powershell-ise?view=powershell-7


Visual Studio Code is recommended as the replacement for Powershell ISE
https://code.visualstudio.com/



Wednesday, January 22, 2020

Using Primary Group IDs



Primary Group IDs are the RIDs for the Domain groups. The full list is here: Interesting Windows Computer & Active Directory Well-Known Security Identifiers (SIDs).

515 – Domain Computers
516 – Domain Controllers (writable)
521 – Domain Controllers (Read-Only)
This information helps filter computer objects to return only the desired computer type.
https://adsecurity.org/?p=873


example

Get-ADComputer -Filter {PrimaryGroupID -eq 515} | where {$_.name -like "?????xw*" -or $_.name -like "DESKTOP*"} | select name | sort name | out-file $outputFile


Friday, December 6, 2019

alternative code not alternative facts



$winver = Get-wmiobject win32_operatingsystem | select -ExpandProperty version

The above produces the same value as the line below.  Below seems a much neater.

$winver = (get-wmiobject Win32_OperatingSystem).version




That's the alternative code but what is an alternative fact !?
Find out here:

Tuesday, August 13, 2019

Users Mailbox status


Quick post about querying a users mailbox status

https://answers.microsoft.com/en-us/msoffice/forum/msoffice_o365admin-mso_exchon-mso_o365b/recipient-type-values/7c2620e5-9870-48ba-b5c2-7772c739c651


This command returns all the users with migrated mailboxes

get-aduser -SearchBase "OU=OU2,OU=OU1,OU=OU0,DC=corp,DC=ad,DC=name,DC=com" -Properties msExchRemoteRecipientType -Filter {name -like "*"} | ?{$_.msExchRemoteRecipientType -eq 4} | select name,samaccountname,msExchRemoteRecipientType 

Friday, August 2, 2019

Function that calls itself

This was an interesting function that I've come across.  It calls itself to work out the list of reports of a user and the reports of any of their reports from a particular AD arrangement where users' managers are held in a manager field.........! er yes, you can read that again now.



function Get-DirectReports([string] $Dn) {
    $Reports = Get-ADUser -LDAPFilter "(&(manager=$dn)(mail=*))" -Properties "mail", "title" -SearchBase "OU=EUC,DC=corp,DC=ad,DC=domain,DC=com"

    if ($Reports) {
        foreach ($Report in $Reports) {
                Write-Output $Report.DistinguishedName

            if ($Report.DistinguishedName -ne $Dn) {
                Get-DirectReports $Report.DistinguishedName
            }
        }
    }
}

Tuesday, April 30, 2019

Add Ribbon Buttons in Outlook


The goal was to place buttons to certain positions in users' Outlook 2016 ribbons.

The code below is a modified version of this blog post.
https://blogs.msdn.microsoft.com/emeamsgdev/2017/02/21/powershell-deploying-outlook-ribbon-customisations/

Outlook ribbon buttons are controlled in the users' roaming or local profile folder locations e.g. %APPDATA%\Microsoft\Office   (depending on the environment configuration)

An example file name is olkexplorer.officeUI.  When a user customises their ribbon, it is written to XML in this file.

The script below enables XML code segments to be either, injected into existing files, or written into new ones where they do not already exist.

In this example an Report Spam button that exists on another ribbon tab is created on the home and message ribbons for easier access.

#
# 
# Add-OutlookReportSpamButton.ps1
#  
# Original concept obtained from https://blogs.msdn.microsoft.com/emeamsgdev/2017/02/21/powershell-deploying-outlook-ribbon-customisations/

#Set Values - the files will receive the XML from the InjectionStings array. i.e. file1 gets the first injection string and so on.
$TargetFiles = @("olkexplorer.officeUI", "olkmailread.officeUI")

$InjectionStrings = New-Object System.Collections.Generic.List[System.Object]
$InjectionStrings.Add("<mso:customUI xmlns:x1=""MimecastServicesForOutlook.AddinModule"" xmlns:mso=""http://schemas.microsoft.com/office/2009/07/customui""><mso:ribbon><mso:qat/><mso:tabs><mso:tab idQ=""mso:TabMail""><mso:group id=""mso_c1.41340A1"" label=""Spam"" insertBeforeQ=""mso:GroupMailDelete"" autoScale=""true""><mso:control idQ=""x1:ui_main_ribbon_gateway_report_spam_phishing_split_button"" imageMso=""GreenBall"" visible=""true""/></mso:group></mso:tab><mso:tab idQ=""x1:adxRibbonTab_c4dc3496375345748858382774b12030""/></mso:tabs></mso:ribbon></mso:customUI>")
$InjectionStrings.Add("<mso:customUI xmlns:x1=""MimecastServicesForOutlook.AddinModule"" xmlns:mso=""http://schemas.microsoft.com/office/2009/07/customui""><mso:ribbon><mso:qat/><mso:tabs><mso:tab idQ=""mso:TabReadMessage""><mso:group id=""mso_c2.13FA7E9B"" label=""Spam"" insertBeforeQ=""mso:GroupRespond"" autoScale=""true""><mso:control idQ=""x1:adxRibbonMenu_eee1842485324bc18a9a64adbd2147fd"" visible=""true""/></mso:group></mso:tab></mso:tabs></mso:ribbon></mso:customUI>")

# xmlns:x1="MimecastServicesForOutlook.AddinModule" had to be added to the $ribbonXml to create the correct blank file.

# We set the Xml to a complete but empty ribbon UI, in case we don't have an existing one to update (in which case we just want the new Xml)
#$ribbonXml = '<mso:customUI xmlns:x1="MimecastServicesForOutlook.AddinModule" xmlns:mso="http://schemas.microsoft.com/office/2009/07/customui"><mso:ribbon><mso:qat/></mso:ribbon></mso:customUI>'
$ribbonXml = "<mso:customUI xmlns:x1=""MimecastServicesForOutlook.AddinModule"" xmlns:mso=""http://schemas.microsoft.com/office/2009/07/customui""><mso:ribbon><mso:qat/></mso:ribbon></mso:customUI>"



# Set Functions
function MergeNodes($sourceNode, $targetNode)
{
Write-Verbose "Merging $($sourceNode.Name) and $($targetNode.Name)"

    if ($sourceNode.Name -eq "mso:customUI")
    {
    # Need to copy namespaces from this element
    ForEach ($attribute in $sourceNode.Attributes)
        {
        if ( $attribute.Name.StartsWith("xmlns:") )
            {
            # Check whether this namespace already exists in target node
            $namespaceExists = $false
            ForEach ($targetAttribute in $targetNode.Attributes)
                {
                if ($targetAttribute.Name -eq $attribute.Name)
                {
                $namespaceExists = $true
                Write-Verbose "Namespace $($attribute.Name) already declared in target node"
                }
                }
            if (!$namespaceExists)
                {
                Write-Verbose "Adding namespace attribute $($attribute.Name): $($attribute.Value)"
                $namespaceAttribute = $ribbonXmlDoc.CreateAttribute($attribute.Name)
                $namespaceAttribute.Value = $attribute.Value
                [void]$targetNode.Attributes.Append($namespaceAttribute)
                }
            }
        }
     }

    if ($sourceNode.HasChildNodes)
        {
        ForEach ($node in $sourceNode.ChildNodes)
            {
            # Do we already have this node in the target Xml node?
            $targetChildNode = $null
            if ($targetNode.HasChildNodes)
                {
                ForEach ($tNode in $targetNode.ChildNodes)
                    {
                    if ( ($tNode.Name -eq $node.Name) -and ($tNode.NamespaceURI -eq $node.NamespaceURI) )
                        {
                        # Check idQ (or id) value
                        $idAttribute = "idQ"
                        if ($tNode.Name.Equals("mso:group")) { $idAttribute = "id" }
                        Write-Verbose "$($tNode.Name): Target $idAttribute = $($tNode.Attributes.ItemOf($idAttribute).Value), Source $idAttribute = $($Node.Attributes.ItemOf($idAttribute).Value)"

                        if ($tNode.Attributes.ItemOf($idAttribute).Value -eq $Node.Attributes.ItemOf($idAttribute).Value)
                            {
                            $targetChildNode = $tNode 
                            break
                            }
                        }
                    }
                }
            if ($targetChildNode -eq $null)
            {
            Write-Verbose "$($node.Name) not found in target, appending"
            Write-Verbose "$($node.Name) namespace is $($node.NameSpaceURI)"
            $targetChildNode = $targetNode.AppendChild($ribbonXmlDoc.ImportNode($node, $False))
            Write-Verbose "Imported $($targetNode.Name) namespace is $($targetNode.NameSpaceURI)"
            #$targetChildNode.NameSpaceURI = $node.NameSpaceURI
            }
            MergeNodes $node $targetChildNode
            }
        }
}



# Actions
$RunCount = -1
foreach ($TargetFile in $TargetFiles)
    {
    $RunCount +=1
    $RibbonXmlFile = "$env:userprofile\AppData\Roaming\microsoft\office\"+$TargetFile
    $XmlToAdd = $InjectionStrings[$RunCount]
    
    $xmlToAddDoc = [Xml]$xmlToAdd
    
    if ([System.IO.File]::Exists($RibbonXmlFile))
        {
        # We have existing ribbon customisations, so load those
        $ribbonXml = Get-Content $RibbonXmlFile -Encoding UTF8
        }
    $ribbonXmlDoc = [Xml]$ribbonXml
       
    #Do it!
    MergeNodes $xmlToAddDoc.DocumentElement $ribbonXmlDoc.DocumentElement
    
    # Now save the updated file
    $ribbonXmlDoc.Save($RibbonXmlFile)
    }


Thursday, November 8, 2018

$LASTEXITCODE


$LASTEXITCODE is a preexisting variable which takes on the exit / error code of a command.

In this snippet powershell calls the vbscript items and adds the exit codes together to produce a final exit code total

$LASTEXITCODE = 0

$ErrorCode = $null

$Install1 = "cscript .\inst1.vbs"
$Install2 = "cscript .\inst2.vbs"

Invoke-Expression $Install1

    $ErrorCode += $LASTEXITCODE

Invoke-Expression $Install2

    $ErrorCode += $LASTEXITCODE

$ErrorCode
exit $ErrorCode