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
}
}
}
}
}
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
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?
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
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
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.
Perhaps you can attach a screenshot ?
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
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"
But where is the Invoke-VMScript ?
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
I was just testing it out before doing the Invoke-VMScript. It should work the same way right?
Yes, it should display the text that is inside the function
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Any ideas why it won't? PowerShell version problem? Why would it not work on my system..
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
I run the ps1 file with PowerShell.
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
LucD,
I have been right clicking the ps1 file and running with PowerShell.
I am going to try with Invoke-VMScript tonight.
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 -
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"
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
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 |
