Functions/GenXdev.AI.ComfyUI/Set-ComfyUIModelPath.ps1

<##############################################################################
Part of PowerShell module : GenXdev.AI.ComfyUI
Original cmdlet filename : Set-ComfyUIModelPath.ps1
Original author : René Vaessen / GenXdev
Version : 1.270.2025
################################################################################
MIT License
 
Copyright 2021-2025 GenXdev
 
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
 
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
################################################################################>

###############################################################################

<#
.SYNOPSIS
Adds a custom model path to ComfyUI's extra_model_paths.yaml configuration
 
.DESCRIPTION
Configures ComfyUI to use an additional custom path for model storage alongside
the default ComfyUI models directory. This function creates or updates the
'custom' section in the extra_model_paths.yaml file, allowing ComfyUI to find
models in additional locations without affecting the default model discovery.
 
The function handles both creating new configuration files and updating existing
ones, ensuring that the custom model path is properly configured as a
supplementary model source. Can also clear the custom configuration when using
the -Clear parameter.
 
This does NOT replace ComfyUI's default model paths - it adds an additional
location where ComfyUI will search for models, VAEs, LoRAs, etc.
 
.PARAMETER ModelPath
The base path to add as an additional model directory. This should be the root
directory that contains subdirectories like checkpoints/, vae/, loras/, etc.
ComfyUI will search this location IN ADDITION to its default model directories.
For example, if you have models organized as:
E:\MyModels\checkpoints\
E:\MyModels\vae\
E:\MyModels\loras\
Then pass "E:\MyModels" as the ModelPath.
 
.PARAMETER Clear
Removes the custom section from the extra_model_paths.yaml configuration file.
 
.PARAMETER Monitor
The monitor to use, 0 = default, -1 is discard
 
.PARAMETER NoBorders
Removes the borders of the window
 
.PARAMETER Width
The initial width of the window
 
.PARAMETER Height
The initial height of the window
 
.PARAMETER X
The initial X position of the window
 
.PARAMETER Y
The initial Y position of the window
 
.PARAMETER KeysToSend
Array of keys to send to the ComfyUI window after ready
 
.PARAMETER SendKeyDelayMilliSeconds
Delay in milliseconds between key send operations
 
.PARAMETER Timeout
Timeout in seconds to wait for ComfyUI server readiness
 
.PARAMETER Left
Place window on the left side of the screen
 
.PARAMETER Right
Place window on the right side of the screen
 
.PARAMETER Top
Place window on the top side of the screen
 
.PARAMETER Bottom
Place window on the bottom side of the screen
 
.PARAMETER Centered
Place window in the center of the screen
 
.PARAMETER FullScreen
Sends F11 to the window
 
.PARAMETER RestoreFocus
Restore PowerShell window focus
 
.PARAMETER SideBySide
Position windows side by side
 
.PARAMETER FocusWindow
Focus the window after opening
 
.PARAMETER SetForeground
Set the window to foreground after opening
 
.PARAMETER Maximize
Maximize the window after positioning
 
.PARAMETER SendKeyEscape
Escape control characters and modifiers when sending keys
 
.PARAMETER SendKeyHoldKeyboardFocus
Hold keyboard focus on target window when sending keys
 
.PARAMETER SendKeyUseShiftEnter
Use Shift+Enter instead of Enter when sending keys
 
.PARAMETER ShowWindow
Show the ComfyUI window during initialization process
 
.PARAMETER Force
Force stop any existing ComfyUI process before starting a new one
 
.PARAMETER MoveModels
Move models from old path to new path when changing model path
 
.EXAMPLE
Set-ComfyUIModelPath -ModelPath "E:\MyModels"
 
Adds E:\MyModels as an additional model search path. ComfyUI will search for:
- Checkpoints in E:\MyModels\checkpoints\
- VAEs in E:\MyModels\vae\
- LoRAs in E:\MyModels\loras\
- Plus all the default ComfyUI model directories
 
.EXAMPLE
Set-ComfyUIModelPath "D:\StableDiffusionModels"
 
Adds D:\StableDiffusionModels as a supplementary model path alongside ComfyUI's
default model locations. Models should be organized in subdirectories:
D:\StableDiffusionModels\checkpoints\
D:\StableDiffusionModels\vae\
etc.
 
.EXAMPLE
Set-ComfyUIModelPath -Clear
 
Removes the custom model path configuration from ComfyUI.
#>

function Set-ComfyUIModelPath {

    [CmdletBinding(DefaultParameterSetName = 'SetPath')]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    param(
        ###############################################################################
        [Parameter(
            Mandatory = $true,
            Position = 0,
            ParameterSetName = 'SetPath',
            HelpMessage = "The base path to set as the model directory"
        )]
        [ValidateNotNullOrEmpty()]
        [string] $ModelPath,
        ###############################################################################
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'Clear',
            HelpMessage = "Remove the custom model path configuration"
        )]
        [switch] $Clear,
        ###############################################################################
        [Parameter(
            Position = 1,
            Mandatory = $false,
            HelpMessage = 'The monitor to use, 0 = default, -1 is discard'
        )]
        [Alias('m', 'mon')]
        [int] $Monitor = -2,
        ###############################################################################
        [Parameter(
            Position = 2,
            Mandatory = $false,
            HelpMessage = 'The initial width of the window'
        )]
        [int] $Width,
        ###############################################################################
        [Parameter(
            Position = 3,
            Mandatory = $false,
            HelpMessage = 'The initial height of the window'
        )]
        [int] $Height,
        ###############################################################################
        [Parameter(
            Position = 4,
            Mandatory = $false,
            HelpMessage = 'The initial X position of the window'
        )]
        [int] $X,
        ###############################################################################
        [Parameter(
            Position = 5,
            Mandatory = $false,
            HelpMessage = 'The initial Y position of the window'
        )]
        [int] $Y,
        ###############################################################################
        [Parameter(
            Position = 6,
            Mandatory = $false,
            HelpMessage = 'Array of keys to send to the ComfyUI window after ready'
        )]
        [string[]] $KeysToSend,
        ###############################################################################
        [Parameter(
            Position = 7,
            Mandatory = $false,
            HelpMessage = 'Delay in milliseconds between key send operations'
        )]
        [Alias('DelayMilliSeconds')]
        [int] $SendKeyDelayMilliSeconds,
        ###############################################################################
        [Parameter(
            Position = 8,
            Mandatory = $false,
            HelpMessage = 'Timeout in seconds to wait for ComfyUI server readiness'
        )]
        [int] $Timeout = 600,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Removes the borders of the window'
        )]
        [Alias('nb')]
        [switch] $NoBorders,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Place window on the left side of the screen'
        )]
        [switch] $Left,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Place window on the right side of the screen'
        )]
        [switch] $Right,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Place window on the top side of the screen'
        )]
        [switch] $Top,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Place window on the bottom side of the screen'
        )]
        [switch] $Bottom,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Place window in the center of the screen'
        )]
        [switch] $Centered,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Sends F11 to the window'
        )]
        [switch] $FullScreen,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Restore PowerShell window focus'
        )]
        [switch] $RestoreFocus,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Position windows side by side'
        )]
        [switch] $SideBySide,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Focus the window after opening'
        )]
        [switch] $FocusWindow,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Set the window to foreground after opening'
        )]
        [switch] $SetForeground,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Maximize the window after positioning'
        )]
        [switch] $Maximize,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Escape control characters and modifiers when sending keys'
        )]
        [switch] $SendKeyEscape,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Hold keyboard focus on target window when sending keys'
        )]
        [switch] $SendKeyHoldKeyboardFocus,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Use Shift+Enter instead of Enter when sending keys'
        )]
        [switch] $SendKeyUseShiftEnter,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Show the ComfyUI window during initialization process'
        )]
        [switch] $ShowWindow,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Force stop any existing ComfyUI process before starting a new one'
        )]
        [switch] $Force,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Move models from old path to new path when changing model path'
        )]
        [switch] $MoveModels
        ###############################################################################
    )

    begin {

        # get the current configured model path
        $current = GenXdev.AI\Get-ComfyUIModelPath

        # only process model path expansion when not clearing
        if (-not $Clear) {

            # expand the model path to full path
            $modelPathExpanded = GenXdev.FileSystem\Expand-Path $ModelPath

            # check if the path is already configured
            if ($current -eq $modelPathExpanded) {

                Microsoft.PowerShell.Utility\Write-Verbose (
                    "The specified model path is already configured: " +
                    "${modelPathExpanded}"
                )
                return
            }

            # move existing models if requested and source path exists
            if ($MoveModels -and
                (Microsoft.PowerShell.Management\Test-Path -LiteralPath $current)) {

                # copy models from old to new location using robocopy
                GenXdev.FileSystem\Start-RoboCopy "${current}\*" (
                    GenXdev.FileSystem\Expand-Path "${modelPathExpanded}\" `
                        -CreateDirectory
                ) -Move
            }
        }

        # define the yaml configuration file path
        $yamlPath = Microsoft.PowerShell.Management\Join-Path `
            $env:LOCALAPPDATA (
                "Programs\@comfyorgcomfyui-electron\resources\ComfyUI\" +
                "extra_model_paths.yaml"
            )

        # ensure the yaml directory structure exists
        $yamlDir = [System.IO.Path]::GetDirectoryName($yamlPath)
        if (-not [System.IO.Directory]::Exists($yamlDir)) {

            $null = [System.IO.Directory]::CreateDirectory($yamlDir)
        }

        # check if comfyui is currently running
        $wasRunning = $false

        if (-not $Clear) {

            $currentExpanded = GenXdev.FileSystem\Expand-Path $current

            if ($modelPathExpanded -ne $currentExpanded) {

                $wasRunning = Microsoft.PowerShell.Management\Get-Process `
                    -Name "ComfyUI" `
                    -ErrorAction SilentlyContinue

                # stop comfyui if it's running to apply changes
                if ($wasRunning) {

                    GenXdev.AI\Stop-ComfyUI
                }
            }
        }
    }

    process {

        # read existing yaml content or initialize empty string
        $yamlContent = if ([System.IO.File]::Exists($yamlPath)) {

            [System.IO.File]::ReadAllText($yamlPath)
        } else {

            ""
        }

        if ($Clear) {

            # remove custom section if it exists in the yaml
            if ($yamlContent -match "custom:") {

                # remove the entire custom section including sub-properties
                $yamlContent = $yamlContent -replace (
                    "(?ms)^custom:.*?(?=^[a-zA-Z_]|\z)"
                ), ""

                # clean up any excessive newlines
                $yamlContent = $yamlContent -replace "\r?\n\r?\n+", "`r`n`r`n"
                $yamlContent = $yamlContent.Trim()

                # delete file if content is now empty
                if ([string]::IsNullOrWhiteSpace($yamlContent)) {

                    if ([System.IO.File]::Exists($yamlPath)) {

                        [System.IO.File]::Delete($yamlPath)
                    }

                    Microsoft.PowerShell.Utility\Write-Verbose (
                        "Removed custom section and deleted empty " +
                        "extra_model_paths.yaml"
                    )
                } else {

                    [System.IO.File]::WriteAllText($yamlPath, $yamlContent)

                    Microsoft.PowerShell.Utility\Write-Verbose (
                        "Removed custom section from extra_model_paths.yaml"
                    )
                }
            } else {

                Microsoft.PowerShell.Utility\Write-Verbose (
                    "No custom section found in extra_model_paths.yaml"
                )
            }
        } else {

            # create the custom section yaml configuration
            $customSection = (
                "custom:`n" +
                " base_path: ${modelPathExpanded}`n" +
                " checkpoints: checkpoints/`n" +
                " vae: vae/`n" +
                " loras: loras/`n" +
                " upscale_models: upscale_models/`n" +
                " embeddings: embeddings/`n" +
                " controlnet: controlnet/"
            )

            if ($yamlContent -notmatch "custom:") {

                # append custom section to existing content
                $yamlContent += "`n${customSection}"

                [System.IO.File]::WriteAllText($yamlPath, $yamlContent)

                Microsoft.PowerShell.Utility\Write-Verbose (
                    "Added custom model path to extra_model_paths.yaml: " +
                    "${modelPathExpanded}"
                )
            } else {

                # update base_path in existing custom section
                $yamlContent = $yamlContent -replace (
                    "(?<=custom:\n\s*)base_path:\s*.+"
                ), "base_path: ${modelPathExpanded}"

                [System.IO.File]::WriteAllText($yamlPath, $yamlContent)

                Microsoft.PowerShell.Utility\Write-Verbose (
                    "Updated existing custom model path in " +
                    "extra_model_paths.yaml: ${modelPathExpanded}"
                )
             | Microsoft.PowerShell.Core\ForEach-Object fulln}
        }
    }

    end {

        # restart comfyui if it was running before the change
        if ($wasRunning) {

            $params = GenXdev.Helpers\Copy-IdenticalParamValues `
                -BoundParameters $PSBoundParameters `
                -FunctionName "GenXdev.AI\EnsureComfyUI"

            GenXdev.AI\EnsureComfyUI @params
        }
    }
}

###############################################################################