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

The ExitCode = 1 seems to indicate something goes wrong with the function on the target machine.

Does the function execute on Server2K81 directly ?


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

0 Kudos
markvm2
Enthusiast
Enthusiast

It does not execute locally. I am reading that single quotes won't even work. I need to use double quotes but I get those split path errors?

0 Kudos
markvm2
Enthusiast
Enthusiast

The Invoke-Expression method I have works locally, but not through Invoke-VMScript.

Below i tried to use the encodedcommand method and I am getting the error where it says it can't find the PowerShell interpreter.

invokeerror.jpg

Add-PSSnapin VMware.VimAutomation.Core

Connect-VIServer -Server 192.168.1.206

$command2 = {

$sc1 = @'

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

                }

            }

        }

    }

}

remove-profile -days 0 -computername localhost -profiles AAFNCRRHOBXXJI -confirm:$false

'@

Invoke-Expression $sc1

}

$command3 = "ipconfig"

$bytes2 = [System.Text.Encoding]::Unicode.GetBytes($command2)

$encodedcommand = [Convert]::Tobase64String($bytes2)

Invoke-VMScript -VM Server2K81 -ScriptText "powershell.exe -EncodedCommand $encodedcommand"

Read-Host "ok"

0 Kudos
markvm2
Enthusiast
Enthusiast

So this works locally with the Invoke-Expressions method..Now I just need to figure out how to drop it to the VM with Invoke-VMScript?

$sc1 = @'

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

                }

            }

        }

    }

}

remove-profile -days 0 -computername localhost -profiles AAFNCRRHOBXXJI -confirm:$false

'@

Invoke-Expression $sc1

0 Kudos
markvm2
Enthusiast
Enthusiast

Well, I give up! I decided to use Copy-VMGuestFile instead and it is working just fine. I wanted this to be self contained in my script without the needs for any extra files or settings. So what I will do is have my script create the PS1 file, copy it to the VM with Copy-VMGuestFile, rune the script, delete it in the VM and then delete it on the host running the script.

0 Kudos
LucD
Leadership
Leadership

Isn't the error message stating where the problem is ?

It seems to say that the account you use (GuestCredential) doesn't have the required permissions to run a script.

Or perhaps the PowerShell engine is not configured to run scripts for that account.

If you logon with that account on the target VM, can you start the PowerShell prompt and execute the script ?


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

GuilhermeAlves
Enthusiast
Enthusiast

Agree Luc,

I think mark have to change the approach to get things work. Maybe testing with simpler scripts and making a step by step with the desired script... That requires a lot of patience for sure!

0 Kudos
markvm2
Enthusiast
Enthusiast

LucD,

The account can run smaller commands through Invoke-VMScript, that is not the problem. btw, that error only happens when I send the command over encoded

$bytes2 = [System.Text.Encoding]::Unicode.GetBytes($command2)

$encodedcommand = [Convert]::Tobase64String($bytes2)

Invoke-VMScript -VM Server2K81 -ScriptText "powershell.exe -EncodedCommand $encodedcommand"

0 Kudos
LucD
Leadership
Leadership

Try like this, that seems to work for me.

$command2 = "Get-Process"
$vm = Get-VM MyVM

$bytes2 = [System.Text.Encoding]::Unicode.GetBytes($command2)
$encodedcommand = [Convert]::Tobase64String($bytes2)
Invoke-VMScript -VM $vm -ScriptText "powershell.exe -EncodedCommand $encodedcommand" -ScriptType bat


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

0 Kudos
HairyWizard
Contributor
Contributor

Hi All,

I know this is an old thread - but I too was having this exact same problem - and have solved it (Huzzah!)

There appears to be an undocumented Character limit for the -scripttext field - it's somewhere around the 2,000 - 3,000 character mark (I didn't have time to endlessly test it to find it) - so passing a simple function (with only a few lines of code) works without issue, passing a complex function fail.

2 Things to add here:

1: It would be really nice and save a LOT of headaches if the scripttext limit was documented somewhere

2: The invoke-vmscript function should parse the -scripttext field and if it exceeds the limit, fail with an error message that is helpful "the scripttext field exceeds the character limit of XXXX - you may wish to streamline your script or transfer the content using the copy-vmguestfile function first"

(or something)

Thanks.

0 Kudos
LucD
Leadership
Leadership

Afaik, it's not related to the Invoke-VMScript cmdlet, but to the underlying OS and ScriptType you select.

  • Bat - the maximum DOS command line is 2047 (pre-Windows XP)
  • Bat - 8191 (Windows XP and later)
  • PowerShell - 8191
  • Bash - Depends on the distro and version. Nowadays it seems to be around 2097152 (check with 'getconf ARG_MAX')


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

0 Kudos