Home > Software engineering >  Function works alone, but not within script
Function works alone, but not within script

Time:11-11

I am creating some tools for active directory management and have run into an issue where IP Addresses are not being converted to hostnames when searching AD through powershell. As a workaround I created this Resolve-Hostname function and if I run it by itself it works just fine. As soon as I put it in my overall script and try calling upon it, it does not convert the IP to a Hostname...

Below is the function I created

function Resolve-Hostname { 

    If ($Workstation -like "*.*.*.*") #Convert IP Address to Hostname
        {
            $NameHost = resolve-dnsname $Workstation | Select-Object -ExpandProperty NameHost -First 1

            $Workstation = ($NameHost -Split {$_ -eq "."})[0]
        }
        }  #close of create the Resolve-Hostname function

Anyone that has any idea how to implement this better feel free to chip in as well, I am by no means an expert.

I am not sure where my issue lies so I will start from the top, any recommendations for optimizing my script would be greatly appreciated.

Sets the size of the window

$pshost = get-host
$pswindow = $pshost.ui.rawui

$newsize = $pswindow.windowsize
$newsize.height= 30
$newsize.width = 60
$pswindow.windowsize = $newsize

Declares the Show-Menu and resolve-host function

#Create functions

function Show-Menu { #Create the Show-Menu function
    param ([string]$Title = 'Admin Tools Menu') #Sets title
    Clear-Host
    Write-Host " "
    Write-Host " "
    Write-Host "`t$Title" -foregroundcolor Blue #Display title of menu
    
    Write-Host "`t1: Add Admin." 
    Write-Host "`t2: Boot Time."
    Write-Host "`t3: User 'Shr_' Groups."
    Write-Host "`t4: Computer Management." 
    Write-Host "`t5: Active Directory."
    Write-Host "`t6: Remove Admin."
    Write-Host "`t7: Disable Workstation in AD"
    Write-Host
    Write-Host "`tQ: Enter 'Q' to quit."
    Write-Host
    Write-Host
} #close of create show menu function

function Resolve-Hostname { 

    If ($Workstation -like "*.*.*.*") #Convert IP Address to Hostname
        {
            $NameHost = resolve-dnsname $Workstation | Select-Object -ExpandProperty NameHost -First 1

            $Workstation = ($NameHost -Split {$_ -eq "."})[0]
        }
        }  #close of create the Resolve-Hostname function

#End of create functions

Implementation of the script below

#Begin Main Menu
do
 {
    Show-Menu #Displays created menu above
    
    $Selection = $(Write-Host "`tMake your selection: " -foregroundcolor Red -nonewline; Read-Host)
    
    
    switch ($selection)
    {
    
    '1' { #Add Admin
    Clear-Host
        $Workstation = $(Write-Host "Workstation\IP Address" -nonewline -foregroundcolor DarkGreen)   $(Write-Host "?: " -NoNewline; Read-Host)  #Declare Workstation/IP Address

    Resolve-Hostname
    $Workstation
    pause

After the pause the script goes through adding the workstation to the admin group but I am fairly certain that portion is not relevant to the problem at hand. There are no errors that pop up, just that the $workstation outputs the IP address instead of resolving into the Hostname as per the resolve-hostname function.

CodePudding user response:

  • You cannot directly modify a variable that lives in the script scope from inside a function called from that scope, because it runs in a child scope.

    • While the child scope can read the parent scope's variables (and its parent's, and so on), writing to them by name only - perhaps surprisingly - creates a function-local variable of the same name - see this answer for more information, and footnote [1] below for a demonstration.
  • While using the $script: scope modifier - $script:Workstation, in your case - would work,[1] or, more generally, Set-Variable-Scope 1 WorkStation ..., accessing variables across scope boundaries is best avoided, in the interest of encapsulation.

    • Instead, make your function output (return) the modified value and let the caller (re)assign the output to a variable in its scope.
    • Similarly, it's better for functions to receive input values as arguments, via declared parameters.
    • Both techniques are shown below.

Redefine your function as follows, to make it (a) accept the value to resolve via a parameter, and (b) output the (potentially) resolved value:

function Resolve-Hostname { 

  param(
    [string] $Workstation # Declare a parameter.
  )

  # Note that $Workstation now refers to the (always local) *parameter* variable.
  If ($Workstation -like "*.*.*.*") { #Convert IP Address to Hostname
    $NameHost = Resolve-DnsName $Workstation | Select-Object -ExpandProperty NameHost -First 1
    # Extract the first "."-based component *and output it*.
    ($NameHost -split '\.')[0]
  } else {
    $Workstation # Already a hostname, output it as-is.
  }

}

You'd then invoke your function as follows:

# Sample value.
$workstation = '142.251.40.238'

# Call the function with the sample value, and capture the
# output in the same local variable.
$workstation = Resolve-HostName $workstation

[1] A quick demonstration of the original problem and the $script: workaround, using a script block ({ ... }) in lieu of a function, invoked with &, the call operator, which runs the block in a child scope:
$i = 42; & { $i = $i 1 }; $i outputs 42, because the $i = ... assignment implicitly created a local $i variable, so the original $i variable in the parent scope is left untouched.
By contrast, $i = 42; & { $script:i = $i 1 }; $i outputs 43, due to $script:i = ... explicitly targeting the script (parent) scope's $i variable.

  • Related