Donnerstag, 27. Juni 2013

PowerShell: export XML content to text file (SCCM Configuration Item)

Hi,
this is one of my favourites scripts i did for SCCM, to manage Desired Configuration Management in a better way, and it was lost for a long time. It helps you to get settings extracted out of an SCCM configurtation item XML-File. Maybe you got some ".cab" files and want to know what settings are in there without using a tool which cannot export those data to a text file or an excel sheet.
What this script does is very simple, it reads the content of an xml file and creates an XML objects which can be used in powershell.

$path = "D:\xml-output.txt" #the text file you want to create
"New file" > $path
$myxml = [XML](get-content "D:\xml-file.xml")
$settings = $myxml.DesiredConfigurationDigest.BusinessPolicy.settings.rootcomplexsetting.ComplexSetting | foreach-object {$_.simplesetting}
"#############################################################################"  >> $path
" All Settings"  >> $path
"#############################################################################"  >> $path
$settings | %{$_.Rules.Rule} | select LogicalName,Operation,OperandA | ft -Property * -AutoSize | Out-String -Width 4000 >> $path
"#############################################################################"  >> $path
"Registry Values"  >> $path
$settings | %{$_.RegistryDiscoverySource} | ft -Property * -AutoSize | Out-String -Width 4000 >> $path
"#############################################################################"  >> $path
"WMI Entries"  >> $path
$settings | %{$_.WqlQueryDiscoverySource} | ft -Property * -AutoSize | Out-String -Width 4000   >> $path
This one is a very specific script for SCCM, but you can edit it and change it to your needs.

Customizing the output that it fits into a table can be done in different ways. The method I used in this example is described here: http://poshoholic.com/2010/11/11/powershell-quick-tip-creating-wide-tables-with-powershell/

Donnerstag, 20. Juni 2013

Problem with VcGuestProcessManager and different vCenter Versions

Hey,
this week we encountered a strange behavior of the scripting class VcGuestProcessManager, which should run a script on virtual machine using its method startProgramInGuest.
The vCO setup is designed to manage two different vCenters (5.0 and 5.1) this does work with the most scriting classes. If you try to run this from a vCO hosted on vCenter 5.0 and try to run a program on virtual machine hosted on vCenter 5.1 it will fail.

The german error message is this one:

Der Vorgang wird für dieses Objekt nicht unterstützt. (Workflow: RunProgramOnGuest / Scriptable task (item0)#22)
In english it is something like that:
The process is not supported to this object
This is roughly translated :D

We figured out that the vCenter version, the vCO is hosted on, has to be the same version as the vCenter version , where the virtual machines is hosted.
 

Sonntag, 16. Juni 2013

vCenter Orchestrator: Mount ISO image into virtual CDrom

Hey there,
it's Sunday and nothing to do, so I decided to solve a task which was ignored for a long time.

The task is to mount an ISO image to a virtual pc, maybe to deploy virtual machines and configure them with prepared images of SCCM for example. I could imagine many use cases to automtically mount images to many machines.
Anyway, I created 3 scripts to find the CD drive, find the correct ISO file at your datastore and finally mount it.

Part 1: find the ISO and its path.

vCO expects a path like : "[datastore1] ISOs/Win8Alpha1.iso"

var ISOpaths = new Array(); //if you have more then one ISO file
var fileQuery = new Array(new VcIsoImageFileQuery());
var querySpec = new VcHostDatastoreBrowserSearchSpec();
querySpec.query = fileQuery;
var myISO = "Win8.iso"; //the ISO you are looking for
var myISOtemp = null; //temp parameter, it is just use to cut the string
var myISOpath = null; // your final path

var task = datastore.browser.searchDatastoreSubFolders_Task("[" + datastore.name + "]", querySpec);
var searchResults = System.getModule("com.vmware.library.vc.basic").vim3WaitTaskEnd(task,false,5);
for (var i in searchResults) {
for (var j in searchResults[i].file) {
ISOpaths.push(searchResults[i].folderPath + searchResults[i].file[j].path);
}
}
for (k in ISOpaths)
{
myISOtemp = ISOpaths[k].substring(ISOpaths[k].search("] ")+2,300)
myISOtemp = myISOtemp.substring(myISOtemp.search("/")+1,300)
System.log(myISOtemp);
if(myISOtemp == "Win8.iso" )
{
myISOpath = ISOpaths[k];
System.log("found :" + myISOpath);
break;
}
}
//http://pubs.vmware.com/vi3/sdk/ReferenceGuide/vim.host.DatastoreBrowser.FileInfo.html

Ok that's the script to find the ISO file, you are free to modify this to your own needs. Maybe you have serveral datastores with the same ISO file or different vCenters.

Part 2: Get the CD drive

This is easy, so there are no comments or explanations needed.

var devices = vm.config.hardware.device;

for ( i in devices )  {
if ( devices[i] instanceof VcVirtualCdrom )  {
System.log("Key: " + devices[i].key);
System.log("Label: " + devices[i].deviceInfo.label);
System.log("controllerKey: " + devices[i].controllerKey);
System.log("unitNumber: " + devices[i].unitNumber);
System.log (devices[i]); // will return the scripting class
}
}
Every information you need for part 3 is in there.

Part 3: Mount the ISO image

var spec = new VcVirtualMachineConfigSpec();
var deviceChange = new Array (new VcVirtualDeviceConfigSpec());
var VirtualDeviceConfigSpec = new VcVirtualDeviceConfigSpec();
//http://www.vmware.com/support/developer/vc-sdk/visdk25pubs/ReferenceGuide/vim.vm.device.VirtualDevice.html
VirtualDeviceConfigSpec.operation = VcVirtualDeviceConfigSpecOperation.edit;
var device = new VcVirtualCdrom();
device.key = 3002; //this could be an input parameter
deviceInfo = new VcDescription();
deviceInfo.label = "CD/DVD drive 1";
deviceInfo.summary = "ISO";
device.deviceInfo = new VcDescription();
device.deviceInfo = deviceInfo;
device.controllerKey = 201; //this could be an input parameter
device.unitNumber = 0; //this could be an input parameter
var CdromIsoBackingInfo = new VcVirtualCdromIsoBackingInfo();
CdromIsoBackingInfo.fileName = myISOpath;
var DeviceConnectInfo = new VcVirtualDeviceConnectInfo();
DeviceConnectInfo.startConnected = false;
DeviceConnectInfo.allowGuestControl = true;
DeviceConnectInfo.connected = true;
DeviceConnectInfo.status = "untried";

VirtualDeviceConfigSpec.device = new VcVirtualCdrom();
VirtualDeviceConfigSpec.device = device;

VirtualDeviceConfigSpec.device.backing = CdromIsoBackingInfo;
VirtualDeviceConfigSpec.device.connectable = DeviceConnectInfo;
deviceChange[0] = VirtualDeviceConfigSpec;
spec.deviceChange = deviceChange;
task = vm.reconfigVM_Task(spec);  //task is an output parameter if you want to start a waiting task
The only important thing you have to know is that when you want to add an attribute which is also an object you cannot do something like this: "device.deviceInfo.label" DeviceInfo has to defined before you add a values.

If you have questions, send me an email or post a commend.

Samstag, 15. Juni 2013

Powershell and vCenter Orchestrator Webservice with Output parameters

Hi,
here is the the script you need to get all output parameters of your vCO workflow when you call it from powershell.
Better you paste it into an editor, because it is very long:

$vmName = $args[0]; #to launch the script from commandline with parameters

if(!($vmName)) # if no paramteres are set, ask for it
    {
        $vmName = read-host "Please Enter a Server Name"
    }


[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
$username = "VCOadmin"
$password = "VCOpassword"
$cred = New-Object System.Management.Automation.PSCredential -ArgumentList @($username,(ConvertTo-SecureString -String $password -AsPlainText -Force))

$webservice = New-WebServiceProxy -uri "http://vcoserver:8280/vmware-vmo-webcontrol/webservice?WSDL"  -Credential $cred
$t = $webservice.getType().namespace

$attributes = New-Object ($t + ".WorkflowTokenAttribute")

$attributes.name = "vmName"
$attributes.value = $vmName
$attributes.type = "String"


#$webservice | Get-member
$wf = $webservice.getWorkflowsWithName("*getHotPlugINfo*","$vmName = $args[0];

if(!($vmName))
    {
        $vmName = read-host "Please Enter ServerName"
    }


$webservice = New-WebServiceProxy -uri "http://VCOserver:8280/vmware-vmo-webcontrol/webservice?WSDL"  -Credential $cred
$t = $webservice.getType().namespace

$attributes = New-Object ($t + ".WorkflowTokenAttribute")

$attributes.name = "vmName"
$attributes.value = $vmName
$attributes.type = "String"

#get all workflows, where the name is like getHotPlugInfo, or what ever your name is
$wf = $webservice.getWorkflowsWithName("*getHotPlugINfo*","VCOadmin","VCOpassword")

#start the vCO workflow by its ID
foreach($i in $wf)

    {
       $infos = New-Object psobject
        if($i.name -eq "getHotplugInfo")
           {
           $resp = $webservice.executeWorkflow($i.id,"VCOadmin","VCOpassword",$attributes)
           $WFid = $resp.id            
           $resp.businessState
           
           do{Start-Sleep -Seconds 2}
           while($webservice.getWorkflowTokenStatus(@($WFid), "VCOadmin","VCOpassword") -eq”running”) #get workflow status until it is finished
           
           $status = $webservice.getWorkflowTokenResult(@($WFid),"VCOadmin","VCOpassword") #get the output parameters of your workflow

                $infos | Add-Member NoteProperty Server $status[0].value
                $infos | Add-Member NoteProperty Memoryhotplug $status[1].value
                $infos | Add-Member NoteProperty CPUHotplug $status[2].value
            
            $infos      
           }
    }

The script is self explaining I think, if you have any questions send me an email.

If you are looking for more information about webservice and vCO visit the vcoportal at http://www.vcoportal.de/

vCenter Orchestrator Code Snippet: get Workflow ID

Hi,
with this code snippet you can easily get the workflow token ID of the currently running workflow:

heres the script, I will explain it afterwards:
var wfToken = null;
try{
var wfTokenURL = workflow.getAnswerUrl().url;
var strwfTokenId1 =  wfTokenURL.substring(wfTokenURL.search("&workflowTokenId=")+17,50000);
var strwfTokenId2 = strwfTokenId1.substring(0,strwfTokenId1.search("&"))
}
catch(objErrMsg)
{
System.error(objErrMsg);
}

Ok, here's the explanation:
"workflow.getAnswerUrl().url" will return something like that:

http://vcoserver:8280/vmo/weboperator/webform.html?cmd=answer&workflowTokenId=8a8989123ed1b4b3013f4992cd5b1e99&returnUrl=http%3A%2F%2F192.168.4.12%3A8280%2Fvmo%2Fweboperator%2Fdefault.html

The yellow part is our ID.

Important is the part before and after our ID, first I split the string in two parts and take the part after &workflowTokenId=(this are the 17 positions I add) to the end.
second thing is taking this part and split it again where the "&" is and get the first part. what you finally got is your ID.

vCenter Orchestrator Code Snippet: get Hot Plug Information (CPU/Memory)

Hey,
last week I wanted to get hot plug information from some virtual machines to include it into a powershell script.
So first thing I did is to create a vCO workflow to get such information like CPU and Memory settings.

Input parameters are only the virtual machine name as a string parameter, because it is delivered by a powershell script.

Input:
vmName (string)

Output:
vmName(string)
CPU(boolean)
Memory(boolean)

Script:
var VMs = VcPlugin.getAllVirtualMachines();

for(i in VMs)
{
if(VMs[i].name == vmName)
{
System.log(VMs[i].name + ' Hot CPU: ' + VMs[i].config.cpuHotAddEnabled);
System.log(VMs[i].name + 'Hot MEM: ' + VMs[i].config.memoryHotAddEnabled);
vmName = VMs[i].name;
CPU = VMs[i].config.cpuHotAddEnabled;
Memory = VMs[i].config.cpuHotAddEnabled;
}
}
That's it :)
http://manuelpaeth.blogspot.de/2013/06/powershell-and-vcenter-orchestrator.html


vCenter Orchestrator Code Snippet: Find Template Virtual Machines

Hey there,
today I want to show you a very usefull snippet if you plan to automate your deployment processes.
Just create an action to run this script as often as you want to, or include it in your workflows as a scriptable task:

here the the script:
var VMs = VcPlugin.getAllVirtualMachines();
for(i in VMs){
try{
if(VMs[i].config.template == true){
System.log(VMs[i].name);
   }
}
catch(e)
{
System.error(e);
}
}
I think it is not necessary to explain the code.
Extend the script by using an array for all found templates, or what ever you want to do with it.

That's it

Dienstag, 9. April 2013

vCenter Orchestrator: First Login to vCO Appliance

Hey there,
I often deployed a vCenter Orchestrator Appliance just for a testing purpose or there is a new release. Normally there shouldn't be any complicated tasks to do, because most facts are written down to its documentation. But what is not covered? Right the first login!
Which error could occure?
The error I got every time I did a new deployment is that my user was not known by vCO even though it was correctly configured in my start up configurtation. Authentification should not be done by the pre configured LDAP server delivered with the appliance, better you use your Active Directory.

If you have a closer look to the log file (you can find the error log in your web configuration) after your failed login (invalig Username/Password) you will find such an entry in your logfile:

2013-04-09 12:17:17.847+0000 ERROR [VcoFactoryFacade] Unable to login (Ex: javax.security.auth.login.LoginException: Cannot login user : vcoadmin, user unknown)
 
The solution is that easy, if you did not restart the whole appliance after configuring your Active Directory configuration, the appliance will still be using its own LDAP server.

Restartting just the service of the configuration server from your web browser does not help. You realy need to restart it from console.
After the appliance is up again you can login with your domain account.

There is one more  stumbling block you might pay attention to. Try to use your userPrincipalName like user@mydomain.com.

Hope this was a good hint to help you.


Samstag, 19. Januar 2013

Extend disks on Windows 2003 remotly

Hey,
there is often the need of extending disks on any server platforms. Unfortunatly it is not possible to do this in an easy way on windows 2003 using the the server/computer manager, when the disk is active and formated.
So, what to do instead?
Use a Windows 2008 or Windows 2008 R2 server. The computer manager there lets you connect to a Windows 2003 server remotly. Go to disk management  and simply extend your disk.
You won't be asked for any issues, it just works :-)

Hope this will help you.

See you on the next post

Donnerstag, 17. Januar 2013

Call Powershell Scripts with Parameters

Hello,
often you need to pass parameters when you call your powershell scripts. This is very easy and quickly done.

By passing arguments to your script you create a kind of array of arguments, correct me if I am wrong.

But to use them, you treat them like an array.

To assign a passed argument to a new variable use this:

$var1 = $args[0]
$var2 = $args[1]

very easy, isn't it??

I prefer to call my scripts not directly, because I dont like to write the whole calling command.
The way i prefer is to use command files.

The only thing you need to write into your file is:

powershell.exe -file "<path>" arg1 arg2 arg3 ...

If you want to log all errors type 2> behind your command:

powershell.exe -file "<path>" arg1 arg2 arg3  2> <path>

There could be one hard failure which could make you desperate.
Your script works fine, your call is also fine, but your paramters are not passed to your script. WHY?

It costs me a lot of time, but at the end the failure is hardly hidden :)

The way I name my command files is call_myscript.cmd. This would work.

If you name your command file call_mysscript.ps1.cmd this would also call your script. But it would not pass your parameters. Don't ask me why.

Hope this helps you.

Using Powershell to call a webservice with a complex data type

Hey,
in the last time I often had to implement webservice calls into my powershell scripts. I havent done this very often in the past, so I needed to look for the correct doing on google.

What I found was lots of tutorials how to call a webservice, using the New-WebServiceProxy commandlet. To be honest, this is not satisfying.
What I was looking for is how to pass arguments of a costum types. And this is what I will show you in this blog post. If you see the solution, it is very simple.

Ok lets start with a additional parameter, the credentials.

First we need to build a credentials object:

$username = "admin"
$password = "adminpassword"
$cred = New-Object System.Management.Automation.PSCredential -ArgumentList @($username,(ConvertTo-SecureString -String $password -AsPlainText -Force))

Next step is to build a variable, which holds the webservice call:

$webservice = New-WebServiceProxy -Uri "http://vco-appliance:8280/vmware-vmo-webcontrol/webservice?WSDL" -Credential $cred
 
The webservice I use in this example is the webservice of the vCenter Orchestrator, a very nice tool to automate your vCenter workflows.

You can browse through your webservice very easy. you just have to pipe your $webservice object to the get-member commandlet -> $webservice | gm

What we need to know at this point is, what kind of namespace is the webservice using. This is important, because we need this to specify the type of our arguments later.
So, how to get the namespace:

$t = $webservice.getType().namespace
#output
$t
 
what you get is something like that:
Microsoft.PowerShell.Commands.NewWebserviceProxy.AutogeneratedTypes.WebServiceP
roxy1vmo_webcontrol_webservice_WSDL
 
Now our basement is build, lets create the arguments we want to pass to the webservice later:
$attributes = New-Object ($t + ".WorkflowTokenAttribute")
$attributes.name = "input"
$attributes.value = " "
$attributes.type = "String"

Maybe you ask now, how does he know that he has to use "WorkflowTokenAttribute" as type. I forget to say WorkflowTokenAttribute is the type of our object.

You can verify your object type by typing:

$attributes | gm  (gm is the short form of get-member)
or
$webservice.getType()

This is easy, the right way would be to have look into the WSDL file. The easier way is to use the error message of powershell. So run your script, passing all arguments you want to pass just as a string.
You will get an error message like that:

Cannot convert argument "3", with value: "123456", for "executeWorkflow" to type "Microsoft.PowerShell.Commands.NewWebserviceProxy.AutogeneratedTypes.WebServiceProxy1vmo_
webcontrol_webservice_WSDL.WorkflowTokenAttribute[]": "Cannot convert the "123456" value of type "System.String" to type Microsoft.PowerShell.Commands.NewWebserviceProxy
.AutogeneratedTypes.WebServiceProxy1vmo_webcontrol_webservice_WSDL.WorkflowTokenAttri
bute[]"."
As you can see powershell tells us, what kind of type it expects.

Now you need to know what parameters should be included in our new object, maybe you know it, have a look to the wsdl, or the way I prefer: Import the webservice into a tool which shows you the content of the wsdl. I use wavemaker to do this.

Wavemaker is cool tool to build your own website using webservices. It is very easy. Everybody will be surprised when you create a selfservice portal within 30min ;). If you are intrested in the piece of software, just download it, it's free.

 Back to our topic:

The last step is to call your webservice with all objects we build until now:
$resp = $webservice.executeWorkflow($parameter1,"vCOuser","userPW",$attributes)
$resp means respons, I use the variable to check if my webservice call was successfull

in some webservices you will get a returncode, status, name, id and many others.

For now we are done. A complete script could look like this:

$username = "admin"
$password = "adminPW"
$cred = New-Object System.Management.Automation.PSCredential -ArgumentList @($username,(ConvertTo-SecureString -String $password -AsPlainText -Force))
$webservice = New-WebServiceProxy -uri "http://vCO-appliance:8280/vmware-vmo-webcontrol/webservice?WSDL"  -Credential $cred
$t = $webservice.getType().namespace
$attributes = New-Object ($t + ".WorkflowTokenAttribute")
$attributes.name = "input"
$attributes.value = " "
$attributes.type = "String"
$resp = $webservice.getWorkflowsWithName("MyWorkflow*","vCOUser","userPW")
foreach( $element in $resp)
    {
        $element.id + "`t" + $element.name
   
$resp = $webservice.executeWorkflow($element,"vCOuser","userPW",$attributes)
$Workflowid = $resp.id
$status = $webservice.getWorkflowTokenStatus($Workflowid,"vCOuser","userPW")
$status
while ( $status -eq "running")
    {
        $status = $webservice.getWorkflowTokenStatus($Workflowid ,"vCOuser","userPW")
        $status
        "running"
    }
   
}

This script gets all workflows, which are starting in its name with "MyWorkflow" and starts it in a loop.

You also can build objects of a complex type:
you need this for example to call a HP service manager webservice.
$t = $webservice.GetType().Namespace
$myComplextype = new-object ($t + ".Request")
$instance = new-object ($t + ".InstanceType")
$model = new-object ($t + ".ModelType")
$keys = new-object ($t + ".KeysType")
$changeID = new-object ($t + ".StringType")

$changeID.Value = "1234"
$keys.ChangeID = $changeID
$model.instance = $instance
$model.keys = $keys

$myComplextype.model = $m