Duplicates in Workflow VM audit - powershell

I'm implementing a VM audit of several vcenters (around 5 of them) where the report is simply a csv of each VM along with a few properties. Although this script would run overnight, I found that it took around 5-6 hours to complete and wanted to increase its efficiency. I learned about workflows and figured it would be faster to audit each vcenter at the same time instead of one by one. It was slower than I expected finishing after about 4 hours. I noticed that there were many duplicates in the data and I can't figure out why that would be; maybe my ideas about how workflow works is flawed. I'm also looking for any tips on raising efficiency in my code. Thanks in advance.
The workflow:
workflow test {
param([string[]]$vcenters, [string]$session, [string]$username, [string]$password)
foreach -parallel($vcenter in $vcenters){
$main = InlineScript{
Add-PSSnapin VMware.VimAutomation.Core
Connect-VIServer -Server $Using:vcenter -User $Using:username -Password $Using:password
$vms = Get-View -ViewType VirtualMachine -Property Name, Summary.Config.GuestFullName, Runtime.Powerstate, Config.Hardware.MemoryMB, Config.Hardware.NumCPU
ForEach($machine in $vms){
$vm = Get-VM -Server $Using:vcenter -Name $machine.Name -ErrorAction SilentlyContinue
$object = New-Object -Type PSObject -Property ([ordered]#{
Name = $machine.Name
GuestOS = $machine.Summary.Config.GuestFullName
PowerState = $machine.Runtime.PowerState
MemoryGB = ($machine.Config.Hardware.MemoryMB / 1024)
CPU = $machine.Config.Hardware.NumCPU
VLAN=(Get-NetworkAdapter -VM $vm |Sort-Object NetworkName |Select -Unique -Expand NetworkName) -join '; '
})
$object| Export-Csv -Append “C:\TestReports\$($vcenter)_TestReport.csv” -NoTypeInformation
}
Disconnect-VIServer - $Using:vcenter -Confirm:$false
}
}
}

With the below changes, maybe it runs quickly enough that you no longer need parallelism or workflow. Not sure if those elements are a cause of the duplication problem. If not, you might need to share more details from your environment for help with that piece.
Get-VM is slow. You're calling it once for each VM, and I don't think you need it at all. Try adding this line after connecting to vCenter
$networks = Get-View -ViewType Network
Replacing your VLAN= line with
VLAN= $networks | ? {$_.VM.Value -contains $machine.MoRef.value} | select -exp Name
And dropping your $vm = Get-VM... line entirely.

Related

PowerShell-script with invoke-command gets stuck on specific computers

I'm writing a PowerShell-Script that reads all shares from all AD-Servers and outputs them into a csv-file. At the same time the script is saving all occuring errors and outputs them into an error-log. The script will be run as a weekly task.
When I run the script, all goes well until it gets to one specific server on which it just loads infinitely. The AD-Server (Windows Server_2012_R2) is inside a normal OU and other servers in this OU work fine.
I believe that there are a few others on which the problem occures as well but I'm not sure how many exactly (about one in 150).
It does not produce an error.
Do you know what could be the Problem here?
Code:
$ErrorActionPreference = 'Continue'
$computers = (Get-Content C:\PowerShell\Shares\serverlist.txt).ForEach({
if(-not [string]::IsNullOrWhiteSpace($_))
{
"$_.domain.com"
}
})
$remoteCode = {
Get-SmbShare | Where-Object Path | Get-Acl |
Select-Object -Property "PSChildName", "Path", "Group", "AccessToString"
}
$results = Invoke-Command -ComputerName $computers -ScriptBlock $remoteCode 2>&1
$errors, $good = $results.Where({$_ -is [System.Management.Automation.ErrorRecord]}, 'Split')
$good | Sort-Object PSComputerName | Select-Object "PSComputerName", "PSChildName", "Path", "Group", #{ Name = "AccessToString"; Expression = { $_.AccessToString -replace("268435456", "FullControl") -replace("-1610612736", "ReadAndExecute")}} | export-csv -path C:\PowerShell\Shares\shares.csv -NoTypeInformation -delimiter ";"
$errors.Exception.Message | Set-Content $error_logfile -Encoding Unicode
Alright I now found the problem. Two servers where frozen and that resulted in the never ending PowerShell script. Now I think I will configure some Sort of timeout, does anyone of you know how I could do that the best way?
Thank you all for your help.

PowerShell Test-Connection -efficient Method

One Bronze medal = Newbi 😊, have scoured this site for years, so a million belated thanks, once again!
Just finished up a few different PowerShell ver 5.1 scripts that perform a test-connection on the same 2000 devices regularly.
Out to a csv file
And they are all slow, some minor speed improvements when tweaking it down to the bare bones.
I’ve scaled the script to the minimal, IPADDRESS and NAME only
I’ve run a few comparison tests against a batch file (ping 123.456.789.001 >> PingTest.txt ) and these tests APPEAR to run quicker than my PS script (will run a full apple to apples test tomorrow), but confident something is amiss in the PS code.
Am looking at arrays, and how piping works, but am afraid I’m putting the cart in front of the horse, or there is something going on with how PS handles the csv format and/or ping list in its memory
My most efficient current script below,
Is the following link pointing me in the right direction,? Specifically the asJob switch
PowerShell Mass Test-Connection
$info = "" | Select IPaddress,Name
$OutputFile = new-item -itemType File -path C:\Temp\Results.csv -force -value "IPaddress,Name`r`n" | out-null
Import-csv C:\Temp\GetList.csv | ForEach-Object {
if ($_.IPaddress) {
if (-Not (Test-Connection -ComputerName $_.IPaddress -Quiet -Count 2 -ErrorAction SilentlyContinue)) {
$info.IPaddress = $_.IPaddress
$info.Name = $_.Name
add-content -value "$($info.IPaddress),$($info.Name)" -path C:\Temp\Results.CSV
}
}
}
The export-csv is faster, thank you. I'm not sure if I have it in the right place, but it works, will run some tests on it tomorrow. I may have to include the passes tests though , still getting my head around the get-job cmdlet.
$info = "" | Select IPaddress,Name,Status
Import-csv c:\Temp\GetList.csv | ForEach-Object {
if($_.Ipaddress) {
if (-Not(Test-Connection -ComputerName $_.IPaddress -Quiet -Count 2 -ErrorAction SilentlyContinue)) {
$info.IPaddress = $_.IPaddress
$info.Name = $_.Name
$info.Status = "Failed"
$info | export-csv C:\Temp\Results1.csv -Force -Notypeinformation -Append
}
}
}
$list = Import-Csv C:\Temp\GetList.csv
ForEach ($item In $list) {
Start-Job -ScriptBlock {
param($item)
if (Test-Connection -Computername $item.IPaddress -Quiet -Count 1) {
Add-Content -value "$($item.IPaddress),$($item.Name),$($item.Stauts)" -Path C:\Temp\xlistlist.csv
$list | Export-csv c:\Temp\xCSvreults.csv -Force -NoTypeInformation -Append
}
} -argumentlist $list
}
Thanks Santiago, I did some exploring here, just ran out of time for the day. THANK YOU you for the code examples they cleared a few things up for me :-) –
Have Computer
Dec 2 at 3:59
Would you mind adding some inline comments so I can follow along? Just trynna understand multi-threading (using runspaces) a bit better. –
Abraham Zinala
Dec 2 at 4:58
1
#AbrahamZinala is there a specific part you're struggling to understand? i'm not an expert on this and wouldn't like to give false information. Might be better to pick up all the parts you don't understand and ask a new question –
Santiago Squarzon
Dec 2 at 12:19
Nah man, sorry, I figured it out lol –
Abraham Zinala
Dec 2 at 13:43
1
#AbrahamZinala the only hard part to understand is the [powershell] instance part imo, and you can think of it as a Start-Job but much faster and less memory consuming, you add your scriptblock with .AddScript({...}) and then the parameters with .AddParameters([hashtablehere]). It's important to save the PSInstance in a variable so that we can get the output out of it and it's handler (Status = $psinstance.BeginInvoke()) to know when the instance has completed. The rest is just pure blueprint, once you have one you can copy paste it :P –
Santiago Squarzon
Dec 2 at 13:50
#HaveComputer I have no idea what your comment means. –
Santiago Squarzon
Dec 5 at 1:10
the 2 solutions presented work much faster, my code however is lacking. –
Have Computer
Dec 5 at 1:16
#HaveComputer I know that, this can perform 50x faster or even more depending on hardware and bandwidth. –
Santiago Squarzon
This is a good place where you can use runspaces, I would love to test if Test-Connection -AsJob performs better than this but for some reason it is not available on PS Core on Linux.
Code below took around 10 seconds to scan 254 IPs using my private network IP range. There is a lot of tweaking that can be done, i.e. you can poke around the $Threshold variable, it's currently running 100 runspaces at a time, Count and TimeoutSeconds for Test-Connection have been set to 2 you can tweak that too.
$results variable can be exported using Export-Csv.
# Change this value for tweaking
$Threshold = 100
$RunspacePool = [runspacefactory]::CreateRunspacePool(1, $Threshold)
$RunspacePool.Open()
# This is for testing, use your CSV here instead
# => $list = Import-Csv C:\Temp\GetList.csv
$list = 1..254 | ForEach-Object {
[pscustomobject]#{
IPAddress = "192.168.1.$_"
Hostname = "ExampleHost$_"
}
}
$scriptBlock = {
param($ip, $hostname)
$params = #{
Quiet = $true
Count = 2
TimeoutSeconds = 2
ComputerName = $ip
}
$status = Test-Connection #params
[pscustomobject]#{
Hostname = $hostname
IPAddress = $ip
Status = ('Failed','Success')[[int]$status]
}
}
$runspaces = foreach($line in $list)
{
$params = #{
ip = $line.IPAddress
hostname = $line.Hostname
}
$psinstance = [powershell]::Create().AddScript($scriptBlock).AddParameters($params)
$psinstance.RunspacePool = $RunspacePool
[pscustomobject]#{
Instance = $psinstance
Status = $psinstance.BeginInvoke()
}
}
while($runspaces.Status.IsCompleted -contains $false)
{
Start-Sleep -Milliseconds 500
}
$results = $runspaces.ForEach({ $_.Instance.EndInvoke($_.Status) })
$RunspacePool.Dispose()
$results Sample:
Hostname IPAddress Status
-------- --------- ------
ExampleHost1 192.168.1.1 Success
ExampleHost2 192.168.1.2 Failed
ExampleHost3 192.168.1.3 Success
ExampleHost4 192.168.1.4 Failed
ExampleHost5 192.168.1.5 Success
ExampleHost6 192.168.1.6 Failed
ExampleHost7 192.168.1.7 Failed
ExampleHost8 192.168.1.8 Failed
ExampleHost9 192.168.1.9 Failed
ExampleHost10 192.168.1.10 Failed
...
...
Start-Job -ScriptBlock { Test-Connection -computername (Get-Content -Path “C:\Temp\GetList.csv”) }
Its lacking the fundamentals, works, DE fast.

How to verify whether a windows server has mountpoint or not using WMI

I am generating a report where I need to find which servers has mountpoints configured on it..
can you help how to get that infor using WMI or powershell.
I mean I need to identify the servers, if mountpoints exists in it.. and also their names....
Get a list of all servers from textfile, AD, etc. and run a foreach loop with something like this:
Get-Wmiobject -query “select name,driveletter,freespace from win32_volume where drivetype=3 AND driveletter=NULL” -computer servername
A quick google search for "windows mount point wmi" would return THIS (source).
Then export the results to CSV, HTML or whatever you need. Your question is lacking a lot of details and any sign of effort from your part, so I can't/won't go any further.
UPDATE: Does this help? It lists mount points(folder paths, not driveletters).
$servers = #("server1","server2","server3","server4","server5")
$servers | % {
$mountpoints = #(Get-WmiObject Win32_MountPoint -ComputerName $_ | Select-Object -ExpandProperty Directory | ? { $_ -match 'Win32_Directory.Name="(\w:\\\\.+)"' }) | % { [regex]::Match($_,'Win32_Directory.Name="(\w:\\\\.+)"').Groups[1].Value -replace '\\\\', '\' }
if($mountpoints.Count -gt 0) {
New-Object psobject -Property #{
Server = $_
MountPoints = $mountpoints
}
}
}
Server MountPoints
------ -----------
{server1} {D:\SSD, C:\Test}

Powershell - Create new line for multiple array objects using Export-csv

I have an odd one that I haven't seen much writing on. Anyway, here goes.
I'm trying to build an array and export it to CSV. The problem is, if there is more than one result returned, I can't figure out how to add it to the CSV as a new line. I am currently using a -join to throw all the results into the same cell, but that's not optimal. What I'd really like to do is add a new row and throw the extra results underneath it in the same column. Does that make sense? Here's what I have now:
# Grab all VMs, put into variable
$vms = Get-VM
# Use to build report
foreach ($vm in $vms){
New-Object PSObject -Property #{
VMName = $vm.Name
UsedSpaceGB = $vm.UsedSpaceGB
StorageAllocatedGB = ($vm.HardDisks.capacitygb | Measure-Object -Sum).Sum
NumberOfCPUs = $cm.NumCpu
MemoryGB = $vm.MemoryGB
Datastores = Get-Datastore -VM $vm
Application = ($vm | Get-Annotation -CustomAttribute Applications -ErrorAction SilentlyContinue).Value | select
} | select VMName,#{label="Application";expression={$_.Application -join ","}},UsedSpaceGB,StorageAllocatedGB,NumberOfCPUs,MemoryGB,#{l="Datastores";e={$_.Datastores -join ","}} | Export-Csv -Path C:\script\VMCapacityUsedByApp.csv -NoClobber -Append -NoTypeInformation
}
By the way, this is using VMware's PowerCLI snapin. Any help is greatly appreciated.
Ok, looks like datastores and applications are the only fields that are going to return arrays according to your previous code. Assuming that the $cm.NumCpu was supposed to be $vm.NumCpu the following code should do what you want. It will figure out if you have more datastores or applications, and then loop through expanding the arrays for those fields creating new records for the same VM listing additional datastores and applications until it runs out of records. I set it to only list all details of a VM on the first record, but I'm sure you can figure out how to alter that if needed. Try this code and see how it looks to you:
# Grab all VMs, put into variable
$vms = Get-VM
# Use to build report
foreach ($vm in $vms){
$TempVM = New-Object PSObject -Property #{
VMName = $vm.Name
UsedSpaceGB = $vm.UsedSpaceGB
StorageAllocatedGB = ($vm.HardDisks.capacitygb | Measure-Object -Sum).Sum
NumberOfCPUs = $cm.NumCpu
MemoryGB = $vm.MemoryGB
Datastores = Get-Datastore -VM $vm
Application = ($vm | Get-Annotation -CustomAttribute Applications -ErrorAction SilentlyContinue).Value
}
$Records = if($TempVM.Application.count -gt $TempVM.Datastores.Count){$TempVM.Application.Count}else{$TempVM.Datastores.Count}
$ExpandedVM = #()
$ExpandedVM += $TempVM|select Name,UsedSpaceGB,StorageAllocatedGB,NumberOfCPUs,MemoryGB,#{l="Datastores";e={$TempVM.Datastores[0]}},#{l="Application";e={$TempVM.Application[0]}}
for($i=1;$i -lt $Records;$i++){$ExpandedVM += $TempVM|select Name,#{l="Datastores";e={$TempVM.Datastores[$i]}},#{l="Application";e={$TempVM.Application[$i]}}}
$ExpandedVM | Export-Csv -Path C:\script\VMCapacityUsedByApp.csv -NoClobber -Append -NoTypeInformation
}
There may be a more elegant way to do it, but that should be functional for you at the very least. I don't have VM machines to test against, or the plugin you use, so I made up data that should be in line with what you're feeding it (strings for all fields except datastores and application both of which have their own array of strings) and ended up with output like this:
Name UsedSpaceGB StorageAllo NumberOfCP MemoryGB Datastores Applicatio
catedGB Us n
---- ----------- ----------- ---------- -------- ---------- ----------
TestVM 250 500 4 16 Store1 Word
TestVM Store2 Excel
TestVM Store3 Access
TestVM Outlook
TestVM2 487 500 4 32 StoreA WoW
TestVM2 StoreB SC2
TestVM2 StoreC D3
TestVM2 StoreD
TestVM2 StoreE
That is what you were looking for I think.

Verifying system partition alignment via scripting

I'm trying to verify that the file system partitions within each of the servers I'm working on are aligned correctly. I've got the following script that when I've tried running will either claim that all virtual servers are aligned or not aligned based on which if statement I use (one is commented out):
$myArr = #()
$vms = get-vm | where {$_.PowerState -eq "PoweredOn" -and $_.Guest.OSFullName -match "Microsoft Windows*" } | sort name
foreach($vm in $vms){
$wmi = get-wmiobject -class "win32_DiskPartition" -namespace "root\CIMV2" -ComputerName $vm
foreach ($partition in $wmi){
$Details = "" | Select-Object VMName, Partition, Status
#if (($partition.startingoffset % 65536) -isnot [decimal]){
if ($partition.startingoffSet -eq "65536"){
$Details.VMName = $partition.SystemName
$Details.Partition = $partition.Name
$Details.Status = "Partition aligned"
}
else{
$Details.VMName = $partition.SystemName
$Details.Partition = $partition.Name
$Details.Status = "Partition not aligned"
}
$myArr += $Details
}
}
$myArr | Export-CSV -NoTypeInformation "C:\users\a411882\Documents\Scripts\PartitionAlignment.csv"
Would anyone know what is wrong with my code? I'm still learning about partitions so I'm not sure how I need to check the starting off-set number to verify alignment.
You're passing a virtual machine object instead of a string to get-wmiObject -ComputerName. When I do that, get-wmiObject throws an RPC error. You might try -computerName $vm.guest.Hostname instead of -computerName $vm.
In the commented line, your use of % should return a remainder, which will always be a whole number or zero. Maybe you were expecting a quotient instead, and wanted to evaluate if it's an integer?
PS C:\temp> (1 / 2) -isnot [int]
True
PS C:\temp> (2 / 1) -isnot [int]
False
Recent Windows OS align their partitions automatically, so there's that. Here's a good post about alignment generally on VMware, including a link to a more detailed discussion of guest partitions.