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
}
}
}
}
}
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
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?
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.
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"
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
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.
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
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!
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"
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
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.
Afaik, it's not related to the Invoke-VMScript cmdlet, but to the underlying OS and ScriptType you select.
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference