1

I have been trying to make a function for hours to only run if it's run by the script and not the terminal.

I have a Profile.ps1 in C:\Users\Alexander\Documents\PowerShell\Profile.ps1 that is run on every terminal instance. Like a linux .bashrc file.

In my Profile.ps1 file I have this.

# Get PowerShell Profile Path
$pwsh = [System.Environment]::GetFolderPath("MyDocuments") + "\powershell"

# Dot source the internal functions I only want to be able to access via script and not terminal.
. $pwsh\internal\funcs.ps1

And inside $pwsh\internal\funcs.ps1 file I have this.

function MyPrivateFunction {
    Write-Host "Hello"
}

I have tried using $MyInvocation.CommandOrigin and it works as expected.

function MyPrivateFunction {
    if ($MyInvocation.CommandOrigin -eq "Runspace") {
        return
    }
    Write-Host "Hello"
}

When I start a new terminal the string "Hello" is written to the screen.

And if I invoke the command MyPrivateFunction like this: PS C:\Users\Alexander> MyPrivateFunction It works and returns nothing.

But the problem I have is I don't want to write the if statement in all of the functions.

It would be a lot of code repetition, and wouldn't be good at all.

If I make a function like this in funcs.ps1:

function Get-Origin {
    if ($MyInvocation.CommandOrigin -eq "Runspace") {
        Exit
    }
}

function MyPrivateFunction {
    Get-Origin
    Write-Host "Hello"
}

It doesn't work. Because when I call MyPrivateFunction it returns as Internal because it gets called by the MyPrivateFunction function.

So even if you type MyPrivateFunction directly in the terminal it would still be treated as internal and still print "Hello".

And I only want it to run if it's ran by the script.

Kinda like pythons: python if __name__ == __main__.

I have also tried using .psm1 modules but it will still print Hello.

I have tried using a .psm1 module to import the function but it will still print Hello due to being in the same scope I would guess.

The only solution I have is writing the if ($MyInvocation.CommandOrigin -eq "Runspace") on every function but I don't think that's a viable option due to a lot of code repetition.

There has to be something better than this.

I also think that this question is unique and has not been asked before. Because I have searched and tried the answer to use PowerShell modules but it doesn't function in the way I want it to be.

What I expect it to do is to only allow to run the MyPrivateFunction in the script and not via powershell terminal.

It's in there due to using dot sourcing the funcs.ps1 file.

One way maybe could work is to run a python script and do everything in there but that would be another procedure.

mklement0
  • 382,024
  • 64
  • 607
  • 775
Alexander
  • 11
  • 3
  • does this help? https://stackoverflow.com/a/75251803/15339544 – Santiago Squarzon Aug 09 '23 at 19:31
  • If I use it on every function yes. But I cannot write it into a function because it will still print "Hello". So I would need to set the if statement on every function I write inside funcs.ps1 I cannot write the if statement into a function and use that function so I would need to put the if statement into every function inside funcs.ps1. – Alexander Aug 09 '23 at 19:37
  • But I guess I could write the if statement in every function I can't find another possible way for now. – Alexander Aug 09 '23 at 19:40
  • Have you tried using modules with `Export-ModuleMember -Function 'PublicFunction1','PublicFunction2', ...`? All functions that are _not_ listed there will be private functions and cannot be called outside of the module. – zett42 Aug 10 '23 at 07:10

2 Answers2

0

If you don't mind losing tab completion (which shouldn't matter since you shouldn't be running functions interactively), one possible solution is to have a script that calls your function script, executing the desired function, but only if it is run via script.

funcs.ps1:

Param(
    [parameter(Position=0)]$FunctionName,
    [parameter(Position=1,ValueFromRemainingArguments= $true)]$FunctionArgs
)
. $pwsh\internal\subfuncs.ps1
if($MyInvocation.CommandOrigin -eq "Runspace"){Write-Warning 'Cannot be run interactively';return}
&$FunctionName @FunctionArgs

Then move all of your functions into subfuncs.ps1. Then you call that in your profile like:

$pwsh\internal\funcs.ps1 MyPrivateFunction

Or, if you don't like having to call the script by name you could make a function to call it, and gatekeep the caller function:

Function Call-Function{
    if($MyInvocation.CommandOrigin -eq "Runspace"){
        Write-Warning 'Cannot be run interactively'
        return
    }
    & $pwsh\internal\funcs.ps1 @args
}

Then in your script just Call-Function myprivatefunction, or if that's too long reduce the function name to cf or something, and it's just cf myprivatefunction.

TheMadTechnician
  • 34,906
  • 3
  • 42
  • 56
  • How would one use an alias for this? like: Set-Alias -Name sayhello -Value Call-Function myprivatefunction? A positional parameter cannot be found that accepts argument 'myprivatefunction'. – Alexander Aug 10 '23 at 14:34
0

I have finally done it! Including using aliases.

C:\Users\Alexander\Documents\PowerShell
├── Profile.ps1
├── internal
│   ├── funcs.ps1
│   └── subfuncs.ps1
└── powershell.config.json

Profile.ps1:

$pwsh = [System.Environment]::GetFolderPath("MyDocuments") + "\powershell"

function Open-Function{
    if($MyInvocation.CommandOrigin -eq "Runspace"){
        Write-Warning 'Cannot be run interactively'
        return
    }
    & $pwsh\internal\funcs.ps1 @args
}

function New-Ln-Link {
    Open-Function "New-SymbolicLink"  @args
}

function New-Admin-Right {
    Open-Function "Update-Rights"
}

function New-Wtd {
    param (
        [string[]]$Arguments
    )

    $targetPath = Join-Path (Get-Location) ($Arguments -join " ")

    # Assuming Open-Function is a custom command to call functions dynamically
    Open-Function "Start-WindowsTerminalInDirectory" $targetPath
}

New-Alias -Name ln -Value New-Ln-Link @args
New-Alias -Name admin -Value New-Admin-Right
New-Alias -Name wtd -Value New-Wtd

subfuncs.ps1:

function Update-Rights {
    if(!([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) {
        $workingDirectory = Get-Location
        Write-Host $MyInvocation.MyCommand.UnboundArguments
        Start-Process -FilePath wt.exe -Verb Runas -ArgumentList "-d `"$($workingDirectory)`" `"$($MyInvocation.MyCommand.UnboundArguments)`""

        Exit
    }
}
function Start-WindowsTerminalInDirectory {
    param (
        [string]$Directory = (Get-Location)
    )
    
    Start-Process -FilePath "wt.exe" -ArgumentList "-d $Directory"
}




function New-SymbolicLink {
    param(
        [string]$Source = (Get-Location).Path,
        [string]$Target
    )

    if (-not $Source) {
        Write-Error "Please provide a valid source path."
        return
    }

    if (-not $Target) {
        Write-Error "Please provide a valid target path."
        return
    }

    $Source = Join-Path (Get-Location).Path $Source

    New-Item -Path $Target -ItemType SymbolicLink -Value $Source
}

funcs.ps1:

Param(
    [parameter(Position=0)]$FunctionName,
    [parameter(Position=1,ValueFromRemainingArguments= $true)]$FunctionArgs
)
. $pwsh\internal\subfuncs.ps1
if($MyInvocation.CommandOrigin -eq "Runspace"){Write-Warning 'Cannot be run interactively';return}
&$FunctionName @FunctionArgs

That's it!

You cannot invoke the Start-WindowsTerminalInDirectory directly in terminal.

Alexander
  • 11
  • 3