Recently when working on an engagement with Johan Arwidmark fellow co-worker at TrueSec and we stumbled across something that unnerved both of us. At some point – for testing purposes only – the ‘Administrator’ account for the domain, and its password had been used for testing various ‘things’ in MDT. Now anyone who’s worked with MDT for while can tell you that there is this wonderful thing in MDT called ‘rules’ and they control everything about the setup of a system.

While this provides really powerful and granular control over what we can have a machine do during setup it also means its entirely possible to use an account that has WAY more permissions to complete a task than are needed. In fact we’ve seen companies accidentally use domain admin accounts, the root administrator account for the domain and even stranger things. However the hard part about this is once you do this you generate bootstrap.ini files and you have to remember where they are especially if you have multiple deployment shares one for testing one for production and more.

So I did some PowerShell scripting to help us out with making sure we found all of the .INI files that used the account we didn’t want to use and a way to update that information.

When writing this script I started with something simple. Just a simple one liner PowerShell that would find any .INI files on a server and check their content for information.

Get-ChildItem -Filter *.ini -Recurse | ForEach{if((Get-Content -Path $_.FullName).Contains("UserID=Administrator")){Write-Host $_.fullname}}

The above looks searches the location you run this from and all child items that meet the criteria of being a .INI file and then for each of those items get their content and look to see if it has the ‘USERID’ attribute within set to Administrator. This of course works fine and dandy, until you run it at the root of C:\ as not an administrator or any number other things that will cause it to tell you no. Below is an example of what happens if you run it as an Administrator – sure it found the bootstrap that used the administrator account – Which is a big security no as it stores and sends the corresponding password in plain text! If you’re doing it you should fix it!

So I took this a step further and realized I wanted to provide a way to find all INI files be they bootstrap or INI that files in a deployment share that might cause you to get a black eye from the security team.

Before I knew it I had ended up with well a few lines of code…

<#
.SYNOPSIS
    This script is for finding a specific piece of information in an INI file that is used for MDT and replacing it. An example of this would be if you need to update 
    the domain join account in all boot files in all deployment shares or update the password in all of the boot files in all deployment shares.

.DESCRIPTION
    This script is intended to be used specifically for finding any uses of a specific term in an INI file for the MDT Bootstrap and to modify it. This script is used at
    your own risk.

.EXAMPLE
    Only Gets info using search base C:DeploymentShare 

    .\Modify-BootStrapINI.ps1 -SearchBase C:\DeploymentShare -SearchTerm UserID=Administrator -GetInfo:$true -UpdateInfo:$false

.PARAMETER GetInfo
    This is a required switch true false switch
        $TRUE - Runs the GET-BOOTSTRAPCONTENT function to retreive information based on search criteria and find all INI's meeting the specified criteria.
        $FALSE - Prevents the 'Get-BootstrapContent function from running to retrevie the location of all INI's meeting the specified criteria
.PARAMETER UpdateInfo
    This is a required true false switch
        $TRUE - runs the set-bootstrapcontent function to exchange/update information based on meeting the specified INI criteria
        $FALSE - Prevents the Set-bootstrapcontent function from running to exchange/update information based on meeting the specified INI criteria

.PARAMETER SearchBase
    This is a non-required string. If it is not set it will begin looking from the current running directroy to find all INI files that match the specified criteria
    if set it will use that path to begin the search. This parameter is recommended to reduce run times. 

.PARAMETER SearchTerm
    this is a required string. if it is not set it will be asked for. This is what is used to look to see if the file contains this. This is an EXACT match using the contains
    method. If there is no exact match it will not return a result. 
    EXAMPLE - "Administrator" =/= "UserID=Administrator"

.PARAMETER ReplaceTerm
    This is not required to run the script but will become required if you attempt to run the set-bootstratpcontent function called by making UpdateInfo $true.

.NOTES
    FileName:    Modify-BootStrapInfo.ps1
    Author:      Jordan Benzing
    Contact:     @JordanTheItGuy
    Created:     2018-09-06
    Updated:     2018-09-06

    Version history:
    1.0.0 - (2018-09-06) Script created
#>
[CmdletBinding()]
Param(
    [parameter(Mandatory = $true)]
    [switch]$GetInfo,
    [parameter(Mandatory = $True)]
    [switch]$UpdateInfo,
    [parameter(Mandatory = $false)]
    [string]$SearchBase,
    [Parameter(Mandatory = $True)]
    [string]$SearchTerm
)

try 
{
    $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
    if($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))
        {
            Write-Verbose -Message "Current user Context is admin continuing"
        }
    else 
    {
        throw "Error - You are not currently running this script as an administrator please close the window and open powershell with eleveated credentials"    
    }
}
catch
{
      Write-Error -Message $_   -ErrorAction Stop
}

function Get-BootStrapContent 
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $False)]
        [string]$SearchBase,
        [Parameter(Mandatory = $True)]
        [string]$SearchTerm
    )
    Begin
    {
        $FileArray = @()
    }
    Process 
    {
        #Begin function to search for INI files that meet the criteria. 
        Write-Verbose -Message "Now Building file list to search through - ignoring and suppressing any INI with access denied"
        if($SearchBase -eq $NULL)
        #If the search base option is NOT specified enter this codeblock
        {
            $Filelist = Get-ChildItem -Filter *.ini -Recurse -ErrorAction SilentlyContinue
            #Get the list of all files starting from the currently running directory with the .INI file extension.
            foreach($File in $Filelist)
            #For each file in the list of .INI files perform the following action. - If later adding a progress bar breakout here
                {
                    $FileName = $File.Name
                    Write-Verbose -Message "Retreiving content for $Filename"
                    $Content = Get-Content -Path $file.FullName -ErrorAction SilentlyContinue
                    #Get the content of the file currently being evaluated - if you cannot get the content silently continue - this is done due to permission errors may
                    #Add more gracefull error handling later on
                    if($Content)
                    #Checks if there was any content within the INI file - originally found that some INI files retrieved had no content 
                    #If you remove this check it will error on the contains method because you cannot call contains on a null string.
                    {
                        if($Content.Contains($SearchTerm))
                        #Evaluates if the content from the INI file contains the search terms if it does enters the loop.
                        {
                            $Item = New-Object -TypeName psobject
                            $item | Add-Member -Type NoteProperty -Name FileName -Value $FileName
                            $item | Add-Member -Type NoteProperty -Name FileLocation -Value $File.FullName
                            $FileArray += $Item
                        }
                    }
                }     
        }
        Else
        #In the event the Search base is specified then enter this code block to search for INI files in a specific path.
        {
            $Filelist = Get-ChildItem -Path $SearchBase -Filter *.ini -Recurse -ErrorAction SilentlyContinue
            #Get the list of all files starting from the specified directory with the .INI file extension.
            foreach($File in $Filelist)
            #For each file in the list of .INI files perform the following action. - If later adding a progress bar breakout here
                {
                    $FileName = $File.Name
                    Write-Verbose -Message "Retreiving content for $Filename"
                    $Content = Get-Content -Path $file.FullName -ErrorAction SilentlyContinue
                    #Get the content of the file currently being evaluated - if you cannot get the content silently continue - this is done due to permission errors may
                    #Add more gracefull error handling later on
                    if($Content)
                    #Checks if there was any content within the INI file - originally found that some INI files retrieved had no content 
                    #If you remove this check it will error on the contains method because you cannot call contains on a null string.
                    {
                        if($Content.Contains($SearchTerm))
                        #Evaluates if the content from the INI file contains the search terms if it does enters the loop.
                        {
                            $Item = New-Object -TypeName psobject
                            $item | Add-Member -Type NoteProperty -Name FileName -Value $FileName
                            $item | Add-Member -Type NoteProperty -Name FileLocation -Value $File.FullName
                            $FileArray += $Item
                            #Generates a PSObject with the information to remove the object and then stores it in an array
                        }
                    }
                }     
        }  
        $FileArray | Format-Table -AutoSize
        }
    }



function Set-BootStrapContent {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $False)]
        [string]$SearchBase,
        [Parameter(Mandatory = $True)]
        [string]$SearchTerm,
        [Parameter(Mandatory = $True)]
        [string]$ReplaceTerm
    )
    Begin {}
    Process {
        #Begin function to search for INI files that meet the criteria that we will make changes to. 
        if($SearchBase -eq $NULL)
        {
            #If the search base option is NOT specified enter this codeblock
            #NOTE - By Default while the function does include this capability - it is NOT called later by the script this must be updated further down to allow this section
            #to be called by the script. 
            $Filelist = Get-ChildItem -Filter *.ini -Recurse -ErrorAction SilentlyContinue
            #Get the list of all files starting from the specified directory with the .INI file extension.
            foreach($File in $Filelist)
                #For each file in the list of .INI files perform the following action. - If later adding a progress bar breakout here
                {
                    $Content = Get-Content -Path $file.FullName -ErrorAction SilentlyContinue
                    #Get the content of the file currently being evaluated - if you cannot get the content silently continue - this is done due to permission errors may
                    #Add more gracefull error handling later on
                    if($Content)
                    #Checks if there was any content within the INI file - originally found that some INI files retrieved had no content 
                    #If you remove this check it will error on the contains method because you cannot call contains on a null string.
                    {
                        if($Content.Contains($SearchTerm))
                        #If the content contains the search term then enter the loop to perform the replacement. 
                        {
                            $Content -replace $SearchTerm , $ReplaceTerm | Set-Content $File.FullName
                            #Replaces the content in the file and sets it. 
                            Write-Host "Replaced $SearchTerm in the file $File.FullName with $ReplaceTerm"
                            #Writes to host what it changed. 
                        }
                    }
                }     
        }
        Else
        {
            $Filelist = Get-ChildItem -Path $SearchBase -Filter *.ini -Recurse -ErrorAction SilentlyContinue
            #Get the list of all files starting from the specified directory with the .INI file extension.
            foreach($File in $Filelist)
            #For each file in the list of .INI files perform the following action. - If later adding a progress bar breakout here
                {
                    $Content = Get-Content -Path $file.FullName -ErrorAction SilentlyContinue
                    #Get the content of the file currently being evaluated - if you cannot get the content silently continue - this is done due to permission errors may
                    #Add more gracefull error handling later on
                    if($Content)
                    #Checks if there was any content within the INI file - originally found that some INI files retrieved had no content 
                    #If you remove this check it will error on the contains method because you cannot call contains on a null string.
                    {
                        if($Content.Contains($SearchTerm))
                        #If the content contains the search term then enter the loop to perform the replacement.
                        {
                            $Content -replace $SearchTerm , $ReplaceTerm | Set-Content $File.FullName
                            #Replaces the content in the file and sets it. 
                            Write-Host "Replaced $SearchTerm in the file $File.FullName with $ReplaceTerm"
                            #Writes to host what it changed. 
                        }
                    }
                }     
        }  
    }
    }

if($GetInfo)
#If the GetINfo Flag was set to true - then it will run through this code block
    {    
        if($SearchBase)
        #if search base was set use this cmd structure
            {
                Get-BootStrapContent -SearchBase $SearchBase -SearchTerm $SearchTerm
            }
        else
        #If search base was not set use this cmd structure
            {
                Get-BootStrapContent -SearchTerm $SearchTerm    
            }

    }

if($UpdateInfo)
#If UpdateInfo was set to true - then run through this code block
    {
        if($SearchBase)
        #if serachbase was set run this cmd structure
            {
                Set-BootStrapContent -SearchBase $SearchBase -SearchTerm $SearchTerm
            }
        else
        #if search base was not set run this cmd structure.
            {
                Write-Error -Message "Due to risk assesment updating any INIS that match without specifiying a search base is not supported"    
            }
    }

 

It’s pretty well commented but lets break it down. Essentially the code has two functions within the script the first is to run and find any *.INI files that have specific information in them. The second is to then when requested find those same .INI files and then replace them. While I could just pass the information through I don’t like just changing things and yes there are probably slightly more efficient ways of storing the found files and then using that stored information if people pick the script up and use it and it becomes popular I’ll come back and add that in. But first lets go over the basics including some examples.

.\Modify-BootStrapInfo.ps1 -SearchBase C:\DeploymentShare -SearchTerm UserID=Administrator -GetInfo:$true -UpdateInfo:$false

The above example – would search everything starting in the C:\DeploymentShare directory (it doesn’t care about the \ at the end) and return the information to you

You can also elect to not specify a search directory in which case it will begin search from the current set location, this might not be your desired behavior so pay close attention to where you are running the script from.

Example, if you run the script using the above method it will search every single .INI file on the C:\ this could take longer than you intended but it will certainly be a thorough inspection. Now there is a second half to this that lets you change the value on all of the .INI files if you should happen to find them. Simply change the UpdateInfo Parameter to $true and the $GetInfo to False

It will then prompt you with what you want to replace with and perform the replace action.

See the above working example. One very import thing to note is that the method use to match the text is called contains() unfortunately this method is case sensitive. So if you do not have an exact match it will not match. Again if this script picks up a lot of traction I will improve and resolve that as well. You can download the script here from the ConfigMgr GitHub

https://github.com/SCConfigMgr/ConfigMgr/blob/master/Modules/Scripts/Modify-BootStrapInfo.PS1

 

(573)

Jordan Benzing

Jordan has been working in the Industry since 2009. Since starting he’s worked with Active Directory, Group Policy, SCCM, SCOM and PowerShell. Jordan currently works in the healthcare industry as an SCCM Infrastructure Team lead supporting over 150,000 endpoints. Most recently his focus has been in SQL Reporting for SCCM, creation of PowerShell scripts to automate tasks and PowerBI.

There are no comments.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.