VMware Cloud Community
markvm2
Enthusiast
Enthusiast

Trouble passing a function to be executed with Invoke-VMScript

I have the below function that I need to run on the remote VM through Invoke-VMScript with "remove-profile -days 0 - -computername localhost -profiles $profilename" this script removes a local profile, you can find out more about it here http://gallery.technet.microsoft.com/scriptcenter/Remove-Profile-787d9188

Now I am having extreme difficultly figuring out how to drop the function with Invoke-VMScript so I can call the function. I usually do "Invoke-VMScript -VM $vmname2 -ScriptText "powershell.exe commands" and it works great, but I can't figure out how to drop a function to that powershell process to call the function. Any ideas? I do not want to do this with a ps1 file or remote share, I want this function to be available to the powershell process i start so I can call it. I have tried a bunch of different things like script blocks and invoke-command but they always break my entire script.

To make it more clear - When i start a powershell process through Invoke-VMScript, I need this function to be there so I can call it. So once the powershell.exe process is running i can just have it execute "remove-profile -days 0 - -computername localhost -profiles $profilename"

function remove-profile { [cmdletbinding(

        SupportsShouldProcess = $true,

        ConfirmImpact="High"

    )]

    param(

        [string[]]$computername="localhost",

        [validaterange(0,9999)][int]$days = 1,

        [string[]]$exceptions = $null,

        [string[]]$profiles = $null

    )

    #function to get profiles

    function Get-Profile {

        [cmdletbinding()]

        param(

            [string]$computername,

            [string[]]$exceptions = $null,

            [string[]]$profiles = $null

        )

        #function to convert array of strings into regex for paths to avoid

        function build-Regex {

            param([string[]]$items)

            $( foreach($item in $items){ "^$item$" } ) -join "|"

        }

        $date = get-date

        #test connection

        if(Test-Connection -ComputerName $computername -BufferSize 16 -count 2 -Quiet){

    

            #Check OS

            Try{

                $opSys = Get-WmiObject Win32_OperatingSystem -computername $computername | select -ExpandProperty version

            }

            Catch{

                Throw "Error: Could not obtain OS version WMI information from $computername"

                Return

            }

            #Find XP profiles

            if ($opSys –like “5*”) {

            

                #define property that we will use to test exceptions / profile regexs against

                $property = "fullname"

        

                #get all profiles on computer using file system

                $allProfiles = Get-Childitem "\\$computername\c$\documents and settings\" -force | ?{$_.PSIsContainer}

            }

            #Find Vista + profiles

            if ($opSys –like “6*”) {

            

                #define property that we will use to test exceptions / profile regexs against

                $property = "localpath"

            

                Try{

                    #get all profiles on computer using WMI

                    $allProfiles = get-wmiobject -computername $computername -class win32_userprofile | ?{ $_.localpath -like "C:\users\*" }

                }

                Catch{

                    Throw "Error gathering profile WMI information from $computername.  Be sure that WMI is functioning on this system and that it is running Windows Vista or Server 2008 or later"

                    Return

                }

            }

            #if specified, filter for profiles

            if($profiles){

                #build regex using provided profiles

                $profileRegex = build-Regex $profiles

                #test profiles against profiles regex

                $allProfiles = $allProfiles | ?{ $(split-path $_.$property -leaf) -match $profileRegex }

            }

            #if specified, filter exceptions

            if($exceptions){

                #build regex using provided exceptions

                $exceptionsRegex = build-Regex $exceptions

                #test profiles against exceptions regex

                $allProfiles = $allProfiles | ?{ $(split-path $_.$property -leaf) -notmatch $exceptionsRegex }

            }

            #Return results

            $allProfiles

        }

        else{

            Throw "Could not connect to $computername"

            Return

        }

    }

    #Get date for profile last access comparison

    $date = get-date

    #Add standard accounts to exclude unless explicitly instructed not to

    if( -not $DontExcludeStandardAccounts ){

        $exceptions += "Administrator", "LocalService", "NetworkService", "All Users", "Default User"

    }

    #loop through provided computers

    foreach($computer in $computername){

    

        #get all the profiles for this computer

        $profilesToRemove = Get-Profile -computername $computer -exceptions $exceptions -profiles $profiles -ErrorAction stop

    

        #if none returned, throw an error and move on to the next computer

        if(-not $profilesToRemove){

            Write-Error "Error: No profiles returned on $computer"

            Continue

        }

        #Get-Profiles returns a directoryinfo object for XP, use this to determine OS.

        if($profilesToRemove[0] -isnot [System.IO.DirectoryInfo]){ $opsys = 6 }

        else{ $opsys = 5 }

        #loop through profiles

        foreach($profile in $profilesToRemove){

    

            #Define path and last access time for profile

            if($opsys -eq 6){

                #Windows 7: convert localpath to remote path remote path.  Currently only handling profiles on C drive

                $path = $profile.localpath.replace("C:","\\$computer\C$")

                $lastAccess = ([WMI]'').ConvertToDateTime($profile.LastUseTime)

            }

            else{

                #Windows XP: define path

                $path = $profile.fullname

                $lastAccess = $profile.lastWriteTime

            }

            #Confirm we can reach $path

            Try {

                get-item $path -force -ErrorAction stop | out-null

            }

            Catch{

                #if we couldnt get the item, display an error and move on to the next profile

                Write-Error "Error: Could not get-item for $path"

                Continue

            }

            #If the profile is older than the days specified, remove it

            if($lastAccess -lt $date.AddDays(-$days)){

        

                #-confirm and -whatif support

                if($pscmdlet.shouldprocess("$path last accessed $lastAccess")){

                

                    #build results object

                    $tempResult = "" | Select ComputerName, Path, lastAccess, Status

               

                    Try{

                        if($opsys -eq 6){

                            #Windows Vista+: remove the profile using WMI

                            $profile.delete()

                        }

                        else{

                            #Windows XP: remove the profile using file system

                            Remove-Item $path -force -confirm:$false -recurse

                        }

                        #Add properties to results object

                        $tempResult.ComputerName = $computer

                        $tempResult.Path = $path

                        $tempResult.LastAccess = $lastAccess

                        $tempResult.Status = "No error"

                    }

                    Catch{

                    

                        #add properties to results object

                        $tempResult.ComputerName = $computer

                        $tempResult.Path = $path

                        $tempResult.LastAccess = $lastAccess

                        $tempResult.Status = "Error removing profile"

                    

                        #WMI delete method or file removal failed.  Write an error, move on to the next profile

                        Write-Error "Error: Could not delete $path last accessed $lastAccess"

                        Continue

                    }

                

                    #display result

                    $tempResult

                }

            }

        }

    }

}

30 Replies
LucD
Leadership
Leadership

One way of doing is, is to place the function and the call to the function in a here-string, and then pass that string on the ScriptText parameter.

$myFunction = @"

function remove-profile { [cmdletbinding(

# The rest of the function

}

# Call the function

remove-profile

"@

Invoke-VMscript -VM $vm -ScriptText $myFunction -ScriptType PowerShell


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos
markvm2
Enthusiast
Enthusiast

That does not seem to work. The function does not get called, it just seems to spit the text of the string out? in ISE all of the text is brown so I am not sure if that is ok?

0 Kudos
LucD
Leadership
Leadership

The text is marked as brown in the ISE because the editor sees the complete text as a string (which it is, it is a here-string).

Try this simplified example

$myFunction = @"
function Test-MyFunction {
  "Inside my function"
}
Test-MyFunction
"@


$vm = Get-VM -Name MyVM
Invoke-VMScript -VM $vm -ScriptText $myFunction -ScriptType PowerShell

The output should be something like this

invoke-vmscript.png


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos
markvm2
Enthusiast
Enthusiast

LucD,

I am doing just as you described and even tried the example you just gave me. However, it does not execute the function. The script just spits out the text in $myFuction.


0 Kudos
LucD
Leadership
Leadership

Perhaps you can attach a screenshot ?


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos
markvm2
Enthusiast
Enthusiast

I put that insto a ps1 file and run it. I hope this is not some silly thing and I'm an idiot. I am still new to PS.

$myFunction = @"

function Test-MyFunction {

  "Inside my function"

}

Test-MyFunction

"@

$myFunction

Read-Host "ok"

herestring1.jpg

0 Kudos
LucD
Leadership
Leadership

But where is the Invoke-VMScript ?


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos
markvm2
Enthusiast
Enthusiast

I was just testing it out before doing the Invoke-VMScript. It should work the same way right?

0 Kudos
LucD
Leadership
Leadership

Yes, it should display the text that is inside the function


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos
markvm2
Enthusiast
Enthusiast

Any ideas why it won't? PowerShell version problem? Why would it not work on my system..

0 Kudos
LucD
Leadership
Leadership

But where do you actually run the code ?

I don't see the PowerShell prompt, nor do I see you calling the .ps1 file.


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos
markvm2
Enthusiast
Enthusiast

I run the ps1 file with PowerShell.

0 Kudos
LucD
Leadership
Leadership

But from where and how do you run the .ps1 file ?

From the PowerCLI prompt, from the PowerShell prompt, from the PowerShell ISE ?

I would like to see how you run the script and the output.


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos
GuilhermeAlves
Enthusiast
Enthusiast

This is interesting to me too, i'll follow you guys.

I think markvm2 is right-clicking the ps1 file and "Run with Powershell" LucD 😃

0 Kudos
markvm2
Enthusiast
Enthusiast

LucD,

I have been right clicking the ps1 file and running with PowerShell.

I am going to try with Invoke-VMScript tonight.

0 Kudos
markvm2
Enthusiast
Enthusiast

Ok for some reason the example script you gave me seems to work ok through Invoke-VMScript. I don't know why?

Anyways, the script I am trying to send is not working with some odd errors -

Here is the Script -

Add-PSSnapin VMware.VimAutomation.Core


Connect-VIServer -Server 192.168.1.206


$myFunction = @"
function Remove-Profile {
[cmdletbinding(
        SupportsShouldProcess = $true,
        ConfirmImpact="High"
    )]
    param(
        [string[]]$computername="localhost",
        [validaterange(0,9999)][int]$days = 1,
        [string[]]$exceptions = $null,
        [string[]]$profiles = $null
    )

    #function to get profiles
    function Get-Profile {
        [cmdletbinding()]
        param(
            [string]$computername,
            [string[]]$exceptions = $null,
            [string[]]$profiles = $null
        )

        #function to convert array of strings into regex for paths to avoid
        function build-Regex {
            param([string[]]$items)
            $( foreach($item in $items){ "^$item$" } ) -join "|"
        }

        $date = get-date

        #test connection
        if(Test-Connection -ComputerName $computername -BufferSize 16 -count 2 -Quiet){

            #Check OS
            Try{
                $opSys = Get-WmiObject Win32_OperatingSystem -computername $computername | select -ExpandProperty version
            }
            Catch{
                Throw "Error: Could not obtain OS version WMI information from $computername"
                Return
            }

            #Find XP profiles
            if ($opSys –like “5*”) {

                #define property that we will use to test exceptions / profile regexs against
                $property = "fullname"

                #get all profiles on computer using file system
                $allProfiles = Get-Childitem "\\$computername\c$\documents and settings\" -force | ?{$_.PSIsContainer}
            }

            #Find Vista + profiles
            if ($opSys –like “6*”) {

                #define property that we will use to test exceptions / profile regexs against
                $property = "localpath"

                Try{
                    #get all profiles on computer using WMI
                    $allProfiles = get-wmiobject -computername $computername -class win32_userprofile | ?{ $_.localpath -like "C:\users\*" }
                }
                Catch{
                    Throw "Error gathering profile WMI information from $computername.  Be sure that WMI is functioning on this system and that it is running Windows Vista or Server 2008 or later"
                    Return
                }

            }

            #if specified, filter for profiles
            if($profiles){
                #build regex using provided profiles
                $profileRegex = build-Regex $profiles

                #test profiles against profiles regex
                $allProfiles = $allProfiles | ?{ $(split-path $_.$property -leaf) -match $profileRegex }
            }

            #if specified, filter exceptions
            if($exceptions){
                #build regex using provided exceptions
                $exceptionsRegex = build-Regex $exceptions

                #test profiles against exceptions regex
                $allProfiles = $allProfiles | ?{ $(split-path $_.$property -leaf) -notmatch $exceptionsRegex }
            }

            #Return results
            $allProfiles

        }
        else{
            Throw "Could not connect to $computername"
            Return
        }
    }

    #Get date for profile last access comparison
    $date = get-date

    #Add standard accounts to exclude unless explicitly instructed not to
    if( -not $DontExcludeStandardAccounts ){
        $exceptions += "Administrator", "LocalService", "NetworkService", "All Users", "Default User"
    }

    #loop through provided computers
    foreach($computer in $computername){

        #get all the profiles for this computer
        $profilesToRemove = Get-Profile -computername $computer -exceptions $exceptions -profiles $profiles -ErrorAction stop

        #if none returned, throw an error and move on to the next computer
        if(-not $profilesToRemove){
            Write-Error "Error: No profiles returned on $computer"
            Continue
        }

        #Get-Profiles returns a directoryinfo object for XP, use this to determine OS.
        if($profilesToRemove[0] -isnot [System.IO.DirectoryInfo]){ $opsys = 6 }
        else{ $opsys = 5 }

        #loop through profiles
        foreach($profile in $profilesToRemove){

            #Define path and last access time for profile
            if($opsys -eq 6){

                #Windows 7: convert localpath to remote path remote path.  Currently only handling profiles on C drive
                $path = $profile.localpath.replace("C:","\\$computer\C$")
                $lastAccess = ([WMI]'').ConvertToDateTime($profile.LastUseTime)

            }
            else{

                #Windows XP: define path
                $path = $profile.fullname
                $lastAccess = $profile.lastWriteTime
            }

            #Confirm we can reach $path
            Try {
                get-item $path -force -ErrorAction stop | out-null
            }
            Catch{
                #if we couldn't get the item, display an error and move on to the next profile
                Write-Error "Error: Could not get-item for $path"
                Continue
            }

            #If the profile is older than the days specified, remove it
            if($lastAccess -lt $date.AddDays(-$days)){

                #-confirm and -whatif support
                if($pscmdlet.shouldprocess("$path last accessed $lastAccess")){

                    #build results object
                    $tempResult = "" | Select ComputerName, Path, lastAccess, Status

                    Try{

                        if($opsys -eq 6){
                            #Windows Vista+: remove the profile using WMI
                            $profile.delete()
                        }
                        else{
                            #Windows XP: remove the profile using file system
                            Remove-Item $path -force -confirm:$false -recurse
                        }

                        #Add properties to results object
                        $tempResult.ComputerName = $computer
                        $tempResult.Path = $path
                        $tempResult.LastAccess = $lastAccess
                        $tempResult.Status = "No error"
                    }
                    Catch{

                        #add properties to results object
                        $tempResult.ComputerName = $computer
                        $tempResult.Path = $path
                        $tempResult.LastAccess = $lastAccess
                        $tempResult.Status = "Error removing profile"

                        #WMI delete method or file removal failed.  Write an error, move on to the next profile
                        Write-Error "Error: Could not delete $path last accessed $lastAccess"
                        Continue
                    }

                    #display result
                    $tempResult
                }
            }
        }
    }
   }
Remove-Profile -computername localhost -profiles NAGKFWMZWKUBRT
"@
$myFunction

Invoke-VMScript -VM Server2K81 -ScriptText $myFunction

Read-Host "ok"

PS C:\Users\mark\Desktop> C:\Users\mark\Desktop\heret2.ps1
Add-PSSnapin : Cannot add Windows PowerShell snap-in VMware.VimAutomation.Core because it is already added. Verify the name of the snap-in and try again.
At C:\Users\mark\Desktop\heret2.ps1:1 char:13
+ Add-PSSnapin <<<<  VMware.VimAutomation.Core
    + CategoryInfo          : InvalidArgument: (VMware.VimAutomation.Core:String) [Add-PSSnapin], PSArgumentException
    + FullyQualifiedErrorId : AddPSSnapInRead,Microsoft.PowerShell.Commands.AddPSSnapinCommand


Name                           Port  User                         
----                           ----  ----                         
192.168.1.206                  443   homelab\mark                 
Split-Path : Cannot bind argument to parameter 'Path' because it is null.
At C:\Users\mark\Desktop\heret2.ps1:81 char:11
+ split-path <<<<  $_.$property -leaf
    + CategoryInfo          : InvalidData: (:) [Split-Path], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.SplitPathCommand

Split-Path : Cannot bind argument to parameter 'Path' because it is null.
At C:\Users\mark\Desktop\heret2.ps1:90 char:11
+ split-path <<<<  $_.$property -leaf
    + CategoryInfo          : InvalidData: (:) [Split-Path], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.SplitPathCommand

function Remove-Profile {
[cmdletbinding(
        SupportsShouldProcess = True,
        ConfirmImpact="High"
    )]
    param(
        [string[]]="localhost",
        [validaterange(0,9999)][int] = 1,
        [string[]] = ,
        [string[]] =
    )

    #function to get profiles
    function Get-Profile {
        [cmdletbinding()]
        param(
            [string],
            [string[]] = ,
            [string[]] =
        )

        #function to convert array of strings into regex for paths to avoid
        function build-Regex {
            param([string[]])
            ^$ -join "|"
        }

         = get-date

        #test connection
        if(Test-Connection -ComputerName  -BufferSize 16 -count 2 -Quiet){

            #Check OS
            Try{
                 = Get-WmiObject Win32_OperatingSystem -computername  | select -ExpandProperty version
            }
            Catch{
                Throw "Error: Could not obtain OS version WMI information from "
                Return
            }

            #Find XP profiles
            if ( –like “5*”) {

                #define property that we will use to test exceptions / profile regexs against
                 = "fullname"

                #get all profiles on computer using file system
                 = Get-Childitem "\\\c$\documents and settings\" -force | ?{.PSIsContainer}
            }

            #Find Vista + profiles
            if ( –like “6*”) {

                #define property that we will use to test exceptions / profile regexs against
                 = "localpath"

                Try{
                    #get all profiles on computer using WMI
                     = get-wmiobject -computername  -class win32_userprofile | ?{ .localpath -like "C:\users\*" }
                }
                Catch{
                    Throw "Error gathering profile WMI information from .  Be sure that WMI is functioning on this system and that it is running Windows Vista
or Server 2008 or later"
                    Return
                }

            }

            #if specified, filter for profiles
            if(){
                #build regex using provided profiles
                 = build-Regex

                #test profiles against profiles regex
                 =  | ?{  -match  }
            }

            #if specified, filter exceptions
            if(){
                #build regex using provided exceptions
                 = build-Regex

                #test profiles against exceptions regex
                 =  | ?{  -notmatch  }
            }

            #Return results


        }
        else{
            Throw "Could not connect to "
            Return
        }
    }

    #Get date for profile last access comparison
     = get-date

    #Add standard accounts to exclude unless explicitly instructed not to
    if( -not  ){
         += "Administrator", "LocalService", "NetworkService", "All Users", "Default User"
    }

    #loop through provided computers
    foreach( in ){

        #get all the profiles for this computer
         = Get-Profile -computername  -exceptions  -profiles  -ErrorAction stop

        #if none returned, throw an error and move on to the next computer
        if(-not ){
            Write-Error "Error: No profiles returned on "
            Continue
        }

        #Get-Profiles returns a directoryinfo object for XP, use this to determine OS.
        if([0] -isnot [System.IO.DirectoryInfo]){  = 6 }
        else{  = 5 }

        #loop through profiles
        foreach(C:\Users\mark\Documents\WindowsPowerShell\Microsoft.PowerShellISE_profile.ps1 in ){

            #Define path and last access time for profile
            if( -eq 6){

                #Windows 7: convert localpath to remote path remote path.  Currently only handling profiles on C drive
                 = C:\Users\mark\Documents\WindowsPowerShell\Microsoft.PowerShellISE_profile.ps1.localpath.replace("C:","\\\C$")
                 = ([WMI]'').ConvertToDateTime(C:\Users\mark\Documents\WindowsPowerShell\Microsoft.PowerShellISE_profile.ps1.LastUseTime)

            }
            else{

                #Windows XP: define path
                 = C:\Users\mark\Documents\WindowsPowerShell\Microsoft.PowerShellISE_profile.ps1.fullname
                 = C:\Users\mark\Documents\WindowsPowerShell\Microsoft.PowerShellISE_profile.ps1.lastWriteTime
            }

            #Confirm we can reach
            Try {
                get-item  -force -ErrorAction stop | out-null
            }
            Catch{
                #if we couldn't get the item, display an error and move on to the next profile
                Write-Error "Error: Could not get-item for "
                Continue
            }

            #If the profile is older than the days specified, remove it
            if( -lt .AddDays(-)){

                #-confirm and -whatif support
                if(.shouldprocess(" last accessed ")){

                    #build results object
                     = "" | Select ComputerName, Path, lastAccess, Status

                    Try{

                        if( -eq 6){
                            #Windows Vista+: remove the profile using WMI
                            C:\Users\mark\Documents\WindowsPowerShell\Microsoft.PowerShellISE_profile.ps1.delete()
                        }
                        else{
                            #Windows XP: remove the profile using file system
                            Remove-Item  -force -confirm:False -recurse
                        }

                        #Add properties to results object
                        .ComputerName =
                        .Path =
                        .LastAccess =
                        .Status = "No error"
                    }
                    Catch{

                        #add properties to results object
                        .ComputerName =
                        .Path =
                        .LastAccess =
                        .Status = "Error removing profile"

                        #WMI delete method or file removal failed.  Write an error, move on to the next profile
                        Write-Error "Error: Could not delete  last accessed "
                        Continue
                    }

                    #display result

                }
            }
        }
    }
   }
Remove-Profile -computername localhost -profiles NAGKFWMZWKUBRT

VM           : Server2K81
ExitCode     : 1
ScriptOutput :
Uid          : /VIServer=homelab\mark@192.168.1.206:443/VirtualMachine=VirtualMachine-vm-34/VMScriptResult=371857150_1/
Length       : 0

The Output -

0 Kudos
markvm2
Enthusiast
Enthusiast

If i put the function in singles quotes with @' '@ the errors go away but the script seems to go back to just spitting out the text, even with Invoke-VMScript.

Strangely, if i run it in ISE, I can then call the function outside of the here string and it works.

btw for anyone that wants to try this, the proper syntax for the function is "Remove-Profile -days 0  -computername localhost -profiles profilename confirm:$false"

0 Kudos
LucD
Leadership
Leadership

In the last lines of your script, you have

$myFunction

Invoke-VMScript -VM Server2K81 -ScriptText $myFunction

The first line explains why the script spits out the complete function.

Leave out that line.

I'm curious to see what Invoke-VMScript produces as output.

You should at least get back some lines, even when there is no output produced.

As you should be able to see with my test function


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos
markvm2
Enthusiast
Enthusiast

I don't get anything back.

This is what it looks like -

VM       : Server2K81
ExitCode : 1

ScriptOutput :

Uid      : /VIServer=homelab\mark@192.168.1.206:443/VirtualMachine=VirtualMachine-vm-34/VMScriptResult=371857150_1/
Length   : 0
0 Kudos