I'm looking to use this command to set annotations on VMware virtual machines.
Set-Annotation -entity $vm -CustomAttribute "Owner" -Value "$owner"
I need the script to read 2 input files during the same loop. One input for the entity name and one for the value.
If we make 2 text files,
file 1 =
vm1
vm2
vm3
file 2 =
john
bob
ken
I need the script to do:
Set-Annotation -entity vm1 -CustomAttribute "Owner" -Value "john"
then
Set-Annotation -entity vm2 -CustomAttribute "Owner" -Value "bob"
I've been able to get different loops to run, but nothing correctly.
Try the following:
# Read VM names and owners into parallel arrays.
$vmNames = Get-Content 'file 1'
$owners = Get-Content 'file 2'
# Loop over the VM names with a pipeline and assign the corresponding owner
# by array index, maintained in variable $i.
$vmNames | % { $i = 0 } `
{ Set-Annotation -entity $_ -CustomAttribute "Owner" -Value $owners[$i++] }
You could streamline this by using Get-Content 'file 1' directly as the start of the pipeline, without the need to collect all lines in array variable $vmNames first.
Related
output of cscript prnmngr.vbs -l
Server name abcd
Printer name \\abcd.com\mailroom
Share name mailroom
Driver name Canon iR-ADV 4225/4235 UFR II
Port name mailroom.com
Comment
Location
Print processor winprint
Data type RAW
Parameters
Attributes 536
Priority 1
Default priority 0
Average pages per minute 0
Printer status Idle
Extended printer status Unknown
Detected error state Unknown
Extended detected error state Unknown
Server name cdef
Printer name \\cdfet.com\mailroom3
Share name mailroom3
Driver name Canon iR-ADV 4225/4235 UFR II
Port name mailroomxxx.com
Comment
Location
Print processor winprint
Data type RAW
Parameters
Attributes 536
Priority 1
Default priority 0
Average pages per minute 0
Printer status Idle
Extended printer status Unknown
Detected error state Unknown
Extended detected error state Unknown
something like (note the modified output property names):
$CustomPrinterobjects = New-Object –TypeName PSObject
$CustomPrinterobjects | Add-Member –MemberType NoteProperty –Name ComputerName –Value "$a"
$CustomPrinterobjects | Add-Member –MemberType NoteProperty –Name Name –Value "$b"
$CustomPrinterobjects | Add-Member –MemberType NoteProperty –Name ShareName –Value "$c"
$CustomPrinterobjects | Add-Member –MemberType NoteProperty –Name DriverName –Value "$d"
$CustomPrinterobjects | Add-Member –MemberType NoteProperty –Name PortName –Value "$e"
where $a, $b, $c,$d, $e represent property values looped over the output of cscript prnmngr.vbs -l
Kory Gill helpfully suggests using the W8+ / W2K12+ Get-Printer cmdlet instead.
Similarly, kuujinbo suggests Get-WmiObject -Class Win32_Printer for earlier OS versions.
In the spirit of PowerShell, both commands returns objects whose properties you can access directly - no need for text parsing.
In case you still have a need to parse the output from cscript prnmngr.vbs -l (if it provides extra information that the cited commands do not), use the following approach - note how much effort is needed to parse the textual output into structured objects:
Given that all information is space-separated and the property name part of each line is composed of varying numbers of tokens, the only predictable way to parse the text is to:
maintain a collection of well-known property names
consider whatever comes after the property name on the line the value.
A PSv3+ solution:
# Map the input property names of interest to output property names,
# using a hashtable.
$propNameMap = #{
'Server name ' = 'ComputerName'
'Printer name ' = 'Name'
'Share name ' = 'ShareName'
'Driver name ' = 'DriverName'
'Port name ' = 'PortName'
}
# Split the output of `cscript prnmngr.vbs -l` into paragraphs and
# parse each paragaph into a custom object with only the properties of interest.
$customPrinterObjs = (cscript prnmngr.vbs -l) -join "`n" -split "`n`n" | ForEach-Object {
$ohtFields = [ordered] #{}
foreach ($line in $_ -split "`n") {
foreach ($propNamePair in $propNameMap.GetEnumerator()) {
if ($line -like ($propNamePair.Key + '*')) {
$ohtFields[$propNamePair.Value] = $line.Substring($propNamePair.Key.length)
}
}
}
[pscustomobject] $ohtFields
}
# Output the resulting custom objects.
$customPrinterObjs
With your sample input, the above yields a 2-element [pscustomobject] array:
ComputerName : abcd
Name : \\abcd.com\mailroom
ShareName : mailroom
DriverName : Canon iR-ADV 4225/4235 UFR II
PortName : mailroom.com
ComputerName : cdef
Name : \\cdfet.com\mailroom3
ShareName : mailroom3
DriverName : Canon iR-ADV 4225/4235 UFR II
PortName : mailroomxxx.com
(cscript prnmngr.vbs -l) -join "`n" collects the output lines from cscript prnmngr.vbs -l in an array and then joins them to form a single multiline string.
-split "`n`n" splits the resulting multiline string into paragraphs, each representing a single printer's properties.
The ForEach-Object script block then processes each printer's properties paragraph:
foreach($line in $_ -split "`n") splits the multiline paragraph back into an array of lines and loops over them.
$ohtFields = [ordered] #{} initializes an empty ordered hashtable (where entries are reflected in definition order on output) to serve as the basis for creating a custom object.
The inner foreach loop then checks each line for containing a property of interest, and, if so, adds an entry to the output hashtable with the output property name and the property value, which is the part that follows the well-known property name on the line.
Finally, the ordered hashtable is output as a custom object by casting it to [pscustomobject].
Calling a vbscript-script from Powershell just feels like a "re-write it in Powershell sort of thing" (not to be judgy). #KoryGill asks an interesting question, "Why not just call Get-Printer"?
But, to your question, you can totally turn that into an object, but you'll have to do some text manipulation:
$printer_stuff = $(cscript prnmngr.vbs -l)
This will create a string array named $printer_stuff where each element has a separate line of output on it. You'll want to make a list of tokens for each printer property e.g., server name, printer name, etc. You'll iterate over the output (in the string array) copying the properties to a PSObject. Here is a cheap example to demonstrate the point:
## Make a list of tokens
$tokens = #('Server name', 'Printer name', 'Share name')
## This will be your printer object
$printer = New-Object -TypeName PSObject
## Parsing the string array and stuffing the good bits into your printer object
foreach ($thing in $printer_stuff[0..17]) {
foreach ($token in $tokens) {
if ($thing -match $token) {
Add-Member -InputObject $printer -MemberType NoteProperty -Name $token -Value $thing.Replace($token, '')
}
}
}
## Here is your object...
$printer
If the prnmgr.vbs script will be returning information on a bunch of printers, you can stuff that $printer object into an an array:
$printers = #()
....
$printers += $printer
You can pull each printer's data out of the string array with something like...
$min = 0
$max = $size
while ($min -lt $printer_stuff.length) {
$printer_stuff[$min..$max]
$min = $max + 1
$max += $size
}
As you can see, this is a big pain in the ass, which is why I suggest just to re-write the thing in Powershell. If you're slick enough to do this bit, you're slick enough to port the vbscript-script.
Good Luck,
A-
I currently have a script that retrieves the last modified date of the .vmx in a VM's datastore in vCenter. I need to make changes to instead use and display the last date in the vmware.log file (located in the same datastore as the .vmx)
I'm not sure how to grab that line and convert it to a XX/XX/XXXX format. In the log file, it shows it as Dec 23 10 for example. If this is not possible, no worries. I just need to pull the last line in the log file and export it to a .csv file. Below is my current code:
add-pssnapin VMware.VimAutomation.Core
# ---------- Only modify the fields in this area -------------
$vCenter = 'qlab-copsmgr' #name of the vCenter
$dataCenter = 'Fly-away Kit' #name of the DataCenter
$outputFile = $vCenter + '-LastDateUsed.csv' #desired output file name
# ---------- No modification is needed in the below code. Do not edit -------------
$columnName = "Name,DataStore,Date Last Used" | Out-File .\$OutputFile -Encoding ascii
Connect-VIServer $vCenter -WarningAction SilentlyContinue
$vmList = Get-VM | where { $_.PowerState -eq “PoweredOff”} | select Name
$vmList = $vmList -replace 'Name : ', '' -replace '#{Name=', '' -replace '}', ''
ForEach ($VM in $vmList)
{
# Get configuration and path to vmx file
$VMconfig = Get-VM $VM | Get-View | select config
$VMXpath = $VMconfig.config.files.VMpathName
# Remove and/or replace unwanted strings
$VMXpath = $VMXpath -replace '\[','' -replace '\] ','\' -replace '#{Filename=','/' -replace '}','' -replace '/','\'
# List the vmx file in the datastore
$VMXinfo = ls vmstores:\$VCenter#443\$DataCenter\$VMXpath | Where {$_.LastWriteTime} | select -first 1 | select FolderPath, LastWriteTime
# Remove and/or replace unwanted strings
$VMXinfo = $VMXinfo -replace 'DatastoreFullPath=', '' -replace '#{', '' -replace '}', '' -replace ';', ',' -replace 'LastWriteTime=', ''
# Output vmx information to .csv file
$output = $VM + ', ' + $VMXinfo
$output
echo $output >> $OutputFile
}
I also needed to pull the last event from the vmware.log file in order to backtrack the power off time for VMs where there is no vCenter event history. I looked at file timestamps but found that some VM processes and possibly backup solutions can make them useless.
I tried reading the file in place but ran into issues with the PSDrive type not supporting Get-Content in place. So for better or worse for my solution I started with one of LucD's scripts - the 'Retrieve the logs' script from http://www.lucd.info/2011/02/27/virtual-machine-logging/ which pulls a VMs vmware.log file and copies it to local storage. I then modified it to copy the vmware.log file to a local temp folder, read the last line from the file before deleting the file and return the last line of the log as a PS object.
Note, this is slow and I'm sure my hacks to LucD's script are not elegant, but it does work and I hope if helps someone.
Note: This converts the time value from the log to a PS date object by simple piping the string timestamp from the file into Get-Date. I've read that this does not work as expected for non-US date formatting. For those outside of the US you might want to look into this or just pass the raw timestamp string from the log instead of converting it.
#Examples:
#$lastEventTime = (Get-VM -Name "SomeVM" | Get-VMLogLastEvent).EventTime
#$lastEventTime = Get-VMLogLastEvent -VM "SomeVM" -Path "C:\alternatetemp\"
function Get-VMLogLastEvent{
param(
[parameter(Mandatory=$true,ValueFromPipeline=$true)][PSObject[]]$VM,
[string]$Path=$env:TEMP
)
process{
$report = #()
foreach($obj in $VM){
if($obj.GetType().Name -eq "string"){
$obj = Get-VM -Name $obj
}
$logpath = ($obj.ExtensionData.LayoutEx.File | ?{$_.Name -like "*/vmware.log"}).Name
$dsName = $logPath.Split(']')[0].Trim('[')
$vmPath = $logPath.Split(']')[1].Trim(' ')
$ds = Get-Datastore -Name $dsName
$drvName = "MyDS" + (Get-Random)
$localLog = $Path + "\" + $obj.Name + ".vmware.log"
New-PSDrive -Location $ds -Name $drvName -PSProvider VimDatastore -Root '\' | Out-Null
Copy-DatastoreItem -Item ($drvName + ":" + $vmPath) -Destination $localLog -Force:$true
Remove-PSDrive -Name $drvName -Confirm:$false
$lastEvent = Get-Content -Path $localLog -Tail 1
Remove-Item -Path $localLog -Confirm:$false
$row = "" | Select VM, EventType, Event, EventTime
$row.VM = $obj.Name
($row.EventTime, $row.EventType, $row.Event) = $lastEvent.Split("|")
$row.EventTime = $row.EventTime | Get-Date
$report += $row
}
$report
}
}
That should cover your request, but to expound further on why I needed the detail, which reading between the lines may also benefit you, I'll continue.
I inherited hundreds of legacy VMs that have been powered off from various past acquisitions and divestitures and many of which have been moved between vCenter instances losing all event log detail. When I started my cleanup effort in just one datacenter I had over 60TB of powered off VMs. With the legacy nature of these there was also no detail available on who owned or had any knowledge of these old VMs.
For this I hacked another script I found, also from LucD here: https://communities.vmware.com/thread/540397.
This will take in all the powered off VMs, attempt to determine the time powered off via vCenter event history. I modified it to fall back to the above Get-VMLogLastEvent function to get the final poweroff time of the VM if event log detail is not available.
Error catching could be improved - this will error on VMs where for one reason or another there is no vmware.log file. But quick and dirty I've found this to work and provides the detail on what I need for over 90%.
Again this relies on the above function and for me at least the errors just fail through passing through null values. One could probably remove the errors by adding a check for vmware.log existance before attempting to copy it though this would add a touch more latency in execution due to the slow PSDrive interface to datastores.
$Report = #()
$VMs = Get-VM | Where {$_.PowerState -eq "PoweredOff"}
$Datastores = Get-Datastore | Select Name, Id
$PowerOffEvents = Get-VIEvent -Entity $VMs -MaxSamples ([int]::MaxValue) | where {$_ -is [VMware.Vim.VmPoweredOffEvent]} | Group-Object -Property {$_.Vm.Name}
foreach ($VM in $VMs) {
$lastPO = ($PowerOffEvents | Where { $_.Group[0].Vm.Vm -eq $VM.Id }).Group | Sort-Object -Property CreatedTime -Descending | Select -First 1
$lastLogTime = "";
# If no event log detail, revert to vmware.log last entry which takes more time...
if (($lastPO.PoweredOffTime -eq "") -or ($lastPO.PoweredOffTime -eq $null)){
$lastLogTime = (Get-VMLogLastEvent -VM $VM).EventTime
}
$row = "" | select VMName,Powerstate,OS,Host,Cluster,Datastore,NumCPU,MemMb,DiskGb,PoweredOffTime,PoweredOffBy,LastLogTime
$row.VMName = $vm.Name
$row.Powerstate = $vm.Powerstate
$row.OS = $vm.Guest.OSFullName
$row.Host = $vm.VMHost.name
$row.Cluster = $vm.VMHost.Parent.Name
$row.Datastore = $Datastores | Where{$_.Id -eq ($vm.DatastoreIdList | select -First 1)} | Select -ExpandProperty Name
$row.NumCPU = $vm.NumCPU
$row.MemMb = $vm.MemoryMB
$row.DiskGb = Get-HardDisk -VM $vm | Measure-Object -Property CapacityGB -Sum | select -ExpandProperty Sum
$row.PoweredOffTime = $lastPO.CreatedTime
$row.PoweredOffBy = $lastPO.UserName
$row.LastLogTime = $lastLogTime
$report += $row
}
# Output to screen
$report | Sort Cluster, Host, VMName | Select VMName, Cluster, Host, NumCPU, MemMb, #{N='DiskGb';E={[math]::Round($_.DiskGb,2)}}, PoweredOffTime, PoweredOffBy | ft -a
# Output to CSV - change path/filename as appropriate
$report | Sort Cluster, Host, VMName | Export-Csv -Path "output\Powered_Off_VMs_Report.csv" -NoTypeInformation -UseCulture
Cheers!
I pray this pays back some of the karma I've used.
Meyeaard
I have made a script that checks line by line and if string is found changes it to desired format
#example input you can use get-content PATH to txt or any file and assign it to $lines variable
$lines = #"
ernfoewnfnsf
ernfoewnfnsf
Dec 23 10 sgdsgdfgsdadasd
"# -split "\r\n"
#checks line by line and if find anything that maches start of the line, one Big letter two small, space, two digits, space, two digits, space
$lines | ForEach-Object{
if ($_ -match "^[A-Z][a-z]{2}\s\d{2}\s\d{2}\s")
{
$match = [convert]::ToDateTime($matches[0])
$_ -replace $matches[0], "$($match.ToShortDateString()) " | out-file { PATH } -APPEND
}
else
{
$_ | out-file { PATH } -APPEND
}
}
just change {PATH} with a filenamePAth and this should work for you
I am trying to find a way to execute a command in powershell and put each line of the result in a different variable to use them later in my script. For example if i execute the below command:
C:\> Get-VMHost -Name hq-esxi-prod-01a.nsx.gss | Get-VM | select Name
I will get the below:
Name
----
HQ-LinServ-01a
HQ-Win2012-01a
HQ-Web-02a
I want to have a script that will add each line in a different variable in a script (excluding the first which is name).
how can i do that.
Thank you for your help.
You can use Set-Variable in a loop to put each value in a separate variable:
$i = 0
... | Get-Vm | Select-Objet -Expand Name | ForEach-Object {
Set-Variable -Name "vm$i" -Value $_
$i++
}
However, that usually isn't good advice. It's more common to put all names in one (array) variable:
$vmList = ...| Get-Vm | Select-Object -Expand Name
so you can access individual names via $vmList[<index>], or (if you need access by some kind of name) in a hashtable:
$i = 0
$vmList = #{}
... | Get-Vm | Select-Objet -Expand Name | ForEach-Object {
$vmList["vm$i"] = $_
$i++
}
Best practice would depend on the particular scenario you need this for, though.
Thank you for your reply,
I have tried you answer but it seems that i am using PowerCLI for VMware it does not include Select-Object -Expand (not sure i had an exception), However your answer have mad me reach to a suitable answer for this.
I have used the below and it worked fine using foreach and adding the values in an array and then reading them as below:
$p1vmname = Get-VMHost -Name hq-esxi-prod-01a.nsx.gss | Get-VM | select Name
$p1vmlist = #()
foreach ($p1line in $p1vmname)
{
$p1vmlist += $p1line
}
$p1 = 0
do {
$x = $p1+1
Write-Host -BackgroundColor:Black -ForegroundColor:Yellow "vm number $x is "$p1vmlist[$p1]"."
$p1++
}
until ($p1 -eq $p1vmc)
}
However when using this the names was not totally correct as they had some additional characters as below:
vm number 1 is #{Name=HQ-Web-01a}
vm number 2 is #{Name=HQ-LinServ-01a}
vm number 3 is #{Name=HQ-Win2012-01a}
so i used split and trim to get rid of these as below and worked fine.
$p1vmname = Get-VMHost -Name hq-esxi-prod-01a.nsx.gss | Get-VM | select Name
$p1vmlist = #()
foreach ($p1line in $p1vmname)
{
$p1vmlist += $p1line
}
$p1 = 0
do {
$x = $p1+1
$p1vmlist[$p1] = ($p1vmlist[$p1]) -split("=") | Select-Object -Last 1
$p1vmlist[$p1] = $p1vmlist[$p1].trimend("}")
Write-Host -BackgroundColor:Black -ForegroundColor:Yellow "vm number $x is "$p1vmlist[$p1]"."
$p1++
}
until ($p1 -eq $p1vmc)
}
Output:
vm number 1 is HQ-Web-01a .
vm number 2 is HQ-LinServ-01a .
vm number 3 is HQ-Win2012-01a .
Thank you so much for your answer that helped me a lot.
I am really enjoying scripting now.
Am new to scripting kindly help me write a script that will connect to VMM and get details such as below.
Name : ABC Machine
CPUCount : 8
Memory : 8192
DynamicMemoryEnabled : False
VHDType : DynamicallyExpanding
MaximumSize : 214748364800
Size : 4194304
Location : C:\ClusterStorage\Volume3\CRB\CRB Test Machine_disk_1.vhdx
Classification : Silver
VHDType : DynamicallyExpanding
MaximumSize : 4748364800
Size : 41304
Location : C:\ClusterStorage\Volume2\CRB\CRB Test Machine_disk_2.vhdx
Classification : Silver
I have been able to get individual commands to get the info however I am not able to make a script that will do it for all VMs and convert disk sizes to GB
My working commands are
Get-SCVirtualMachine -Name "ABC Machine" | select Name, CPUCount, Memory, DynamicMemoryEnabled | fl
$DiskINfo = Get-SCVirtualDiskDrive -VMMServer "abc.abcgroupcloud.com" -VM "ABC Machine"
$DiskINfo.VirtualHardDisk | select VHDType, MaximumSize, Size, Location, Classification
1- create an array with all the VM names (or read it from a file with get-content)
2- use a foreach loop to excecute you script over all these VM
3- use a calulated property to display the size in Gb
$computers=#("ABC machine","XYZ machine")
$computers | foreach-object {
Get-SCVirtualMachine -Name $_ | select Name, CPUCount, Memory, DynamicMemoryEnabled | fl
$DiskINfo = Get-SCVirtualDiskDrive -VMMServer "abc.abcgroupcloud.com" -VM $_
$DiskINfo.VirtualHardDisk | select VHDType, MaximumSize, #{Name="Size in Gb";Expression={$($_.size)Mb / 1Gb}}, Location, Classification
}
Old question, but just to add some info.
This will get all the Virtual Machines in your host group in VMM, after entering the correct host group name.
$VMs will be the array, which will contain all the details you are after.
$hg = Get-SCVMHostGroup -Name "My Hostgroup Name"
$hosts = Get-SCVMHost -VMHostGroup $hg
$VMs = $null
ForEach ($h in $hosts)
{
$VMs += Get-SCVirtualMachine -VMHost $h
}
im writing a simple function in order to list subfolders of a particular server share. My problem is that i want all informations in the same array of custom objects. Here is what i did:
function Get-tDollar {
param(
[string]$srv
)
$share = Get-WmiObject -ComputerName $srv -Class Win32_Share -Filter "Name='share`$'" | Select __SERVER,#{n="nPath";e={"\\" + $_.__SERVER + ($_.Path -replace "C:")}}
$inc = 0
Get-ChildItem $share.nPath | % {
$inc++
Add-Member -InputObject $share -MemberType NoteProperty -Name "folder_$inc" -Value $_.Name
}
$share
}
And i use this function like that: #("srv1","srv2") | % { Get-TDollar -srv $_ }
All works fine when the number of subfolders is the same, but when its different, the array contains only the number of folders listed in the first share. For example, for my two first servers, i have this output:
__SERVER nPath folder_1
-------- ----- ------------
srv1 \\srv1\share scripts
srv2 \\srv2\share scripts
But, because srv2 has more folders than the first server, i want this output:
__SERVER nPath repertoire_1 repertoire_2
-------- ----- ------------ ------------
srv1 \\srv1\share scripts
srv2 \\srv2\share scripts config
I know i can first calculate which server has the higher number of folders and then place it in fisrt position, but it seems there are enough lines for something like that. Is there a more efficient/elegant way to do that?
You're getting bitten by the default formatting gremlin:
http://blogs.msdn.com/b/powershell/archive/2006/04/30/586973.aspx
Get-tDollar |
foreach { $_ | format-table }