Grabbing assigned VM names by entering username in Vmware Horizon (PowerCLI) - powershell

I've been working on a PowerShell code to grab VM names from all desktop pools.
I'm using PowerCLI with PowerShell 7 installed.
I have managed to get an output of all the users and their assigned machines. But, I'm having difficulties in optimizing the code in a way that I can input a single user name and it will only show me that user's assigned VM.
Here is the code I've got so far
#Import-Module VMware.VimAutomation.Core
#Import-Module Activedirectory
Connect-VIServer -server servername123 -Force
Connect-HVServer -server server.name.123
$uname = Read-Host -Prompt 'Input the user name you wish to find:' #User-Input
$Global:DefaultHVServers
$query = New-Object "Vmware.Hv.QueryDefinition"
$query.queryEntityType = 'SessionLocalSummaryView'
$qSrv = New-Object "Vmware.Hv.QueryServiceService"
$PCs = ($qSRv.QueryService_Query($global:DefaultHVServers[0].ExtensionData,$query) |
Select -ExpandProperty Results |
Select -ExpandProperty NamesData |
Select-Object -Property UserName,MachineOrRDSServerName)
$PCs | % {"$($_.UserName.Split("\")[1]) `t`t $($_.MachineOrRDSServerName) "}
In the last line of the code, I am formatting the table to remove unnecessary content.
$PCs | % {"$($_.UserName.Split("\")[1]) `t`t $($_.MachineOrRDSServerName) "}
Can someone help me in grabbing username from console and only displaying the VMs that they are assigned to?
I have tried googling for a solution but couldn't find anything relevant.
Thanks!!
Note: I have declared a variable uname but haven't used it yet. I'm unsure how can I use it in this usecase.

After digging around more in the deepest subreddits, I found a post that solved my question.
https://www.reddit.com/r/vmware/comments/d547nt/horizon_view_powercli_help/
Below is the code which utilizes QueryFilterEquals
from VMware.Hv.Equals class to grab usernames and their properties.
I'm skipping the connections portion of the code, it is the same mentioned in the question.
#Get User Input for UserName
$UserName = Read-Host -Prompt 'Input the user name you wish to find:'
#Create Horizon Services object
$HorizonServerServices = $global:DefaultHVServers[0].ExtensionData
#Create Query Definition object with EntityType SessionLocalSummaryView
$HorizonQuery = New-Object VMware.Hv.QueryDefinition
$HorizonQuery.QueryEntityType = 'SessionLocalSummaryView'
#Create Query Filter Object
$QueryFilterEquals = New-Object VMware.Hv.QueryFilterEquals
$QueryFilterEquals.MemberName = 'namesData.userName'
$QueryFilterEquals.value = "domain.loc\$UserName"
$HorizonQuery.Filter = $QueryFilterEquals
$HorizonQueryService = New-Object VMware.Hv.QueryServiceService
$SearchResult = $HorizonQueryService.QueryService_Query($HorizonServerServices, $HorizonQuery)
if ($SearchResult.Results)
{
$SearchResult.Results.Namesdata
}
We do not have to delete the query at the end as it doesn't consume any server-side resources. It is a virtual query. Refer to the link given below for detailed info on how QueryService works.
Refer to: https://vdc-download.vmware.com/vmwb-repository/dcr-public/e2e25628-4ed2-43fc-8bad-54fb86f3bb0f/8e4d2491-c740-4778-ac43-ba8fc0ec8175/doc/queries-landing.html

Related

Change a Windows product key remotely with PowerShell

I'm trying to install/activate a MAK key on remote servers. All of them have RemotePS enabled and firewall exception rules in place.
$Results = Invoke-Command -ComputerName Server1 {
$Props = #{ComputerName = $env:ComputerName}
slmgr.vbs /ipk "12345-12345-12345-12345-12345"
$LicStatus = slmgr.vbs /dlv
$Props.Add('LicenseStatus',$LicStatus)
New-Object -TypeName PSObject -Property $Props
}
$Results | Select-Object ComputerName,LicenseStatus
The above does install the MAK key but I don't get any confirmation of this process which is why I've tried adding in the license check option (/dlv) but get nothing returned in the LicenseStatus field. I'm assuming this is because it returns a multi-value maybe!?
Ultimately I'm just trying to get confirmation that the key was installed. There are articles out there about performing this using RemotePS but they all say a notification message is returned for each computer which isn't the case in my experience: https://4sysops.com/archives/change-a-product-key-remotely-with-powershell/
Any ideas how I can check this?
I would call the slmgr.vbs script using Cscript.exe in order to get the results as string array. Otherwise the system will default to using Wscript.exe which is designed to output everything in a messagebox.
Unfortunately, all output of slmgr is localized, so using a regex or something on the LicenseStatus is a no go (on a Dutch NL machine it reads 'Licentiestatus')
What you can do is using switch /dli, because that returns a string array where the last (not empty) value has the status.
Try
$Results = Invoke-Command -ComputerName Server1 {
# install MAK key
$null = cscript.exe "$env:SystemRoot\System32\slmgr.vbs" /ipk "12345-12345-12345-12345-12345"
# test LicenseStatus
$LicStatus = (((cscript.exe "$env:SystemRoot\System32\slmgr.vbs" /dli) |
Where-Object { $_ -match '\S' })[-1] -split ':', 2)[1].Trim()
# return an object
[PsCustomObject]#{
ComputerName = $env:COMPUTERNAME
LicenseStatus = $LicStatus
}
}
$Results

PowerShell Script to implement ipsec rule

I am troubleshooting an issue in our local IT infrastructure. Some time ago a GPO was pushed that blocked traffic from our IT administration program to our production devices.
Long story short the big company made a decision which wrongly affects our very specific IT needs/design in our department.
Due to sheer coincidence we managed to resolve the issue by manually adding an IPSEC security exception on a device to solve a different issue.
Now the below dodgy attempt by me to make a PS command is just a base as the correct parameters are still to be decided after meeting with multiple sides of the business and IT.
But to reduce the time I need to implement the solution on hundreds of our devices I would like to get a script working where I just have to add or adjust the parameters when I receive the word "go"
I would need the command below to be useable with an input (list/array) of all our devices. I am looking into the CimSession cmdlet but I struggle to come up with a solution to loop through a list/array and add both the target computer and its IP address to the script.
Thank you in advance for your tips on how to proceed.
With the responses below I have expanded the script to the following:
```Powershell
# Ask for the csv file
$CsvLocation = Read-Host -Prompt 'input the location of the csv file (for
example c:\Users\USERNAME\Documents\workstations.csv)'
$CsvFile = Import-CSV -Path $CsvLocation
# Create empty Hash Table
$Machines = #{Workstation = "Test" ; IP = "123"}
# create a hashtable to store the parameters in for splatting
$ruleParams = #{
Confirm = $false
Enabled = $true
Description = 'This rule is instated to allow MobiControl
Administration to be performed on this device.'
DisplayName = 'MobiControl connection'
IPsecRuleName = 'Mobicontrol connection'
OutboundSecurity = 'None'
InboundSecurity = 'None'
Platform = '6.1+'
PolicyStore = 'PersistentStore'
Profile = 'Any'
RemoteAddress = '10.133.120.207'
RequireAuthorization = $false
Protocol = 'any'
}
# For each Element in the csv file add name and ip address to the hash
table
$CsvFile | ForEach-Object {
$Workstation = $_.Workstation
$IpAddress = [System.Net.Dns]::GetHostAddresses($Workstation) |
Where-Object { $_.AddressFamily -eq 'InterNetwork' } | Select-Object -
ExpandProperty IpAddressToString
$Machines.add($Workstation, $IpAddress)
# fill in the two remaining parameters with the IP address and computer
name
<# test print contents
Read-Host "press enter to see the values for hostname and ip address"
Echo $Machines.keys
Read-Host "press enter to continue"
#>
$ruleParams['LocalAddress'] = $_.Value # IP Address
$ruleParams['CimSession'] = $_.Key # HostName
# execute using the ruleParams splat
Write-Host "Creating IPsecRule on computer $() with IP address $()"
# New-NetIPsecRule #ruleParams
}
This looks to be more in the direction I want. Any obvious flaws?
the input csv file would just be a list of workstation names.
Testing the code all seems to be in working order up until the execution of the New-NetIPsecRule. The values inside the hashtable $Machines are non valid inputs for their related parameters.
The way you add the parameters to the cmdlet is incorrect and would require the much hated backticks at the end of each line, preceeded with a space.
Similar that, but much better is to use Splatting
# create a hashtable to store the parameters in for splatting
$ruleParams = #{
Confirm = $false
Enabled = $true
Description = 'This rule is instated to allow MobiControl Administration to be performed on this device.'
DisplayName = 'MobiControl connection'
IPsecRuleName = 'Mobicontrol connection'
OutboundSecurity = 'None'
InboundSecurity = 'None'
Platform = '6.1+'
PolicyStore = 'PersistentStore'
Profile = 'Any'
RemoteAddress = '10.133.120.207'
RequireAuthorization = $false
# I'm not sure about the Protocol parameter..
# The docs say it is a String, but also refer to the Get-NetFirewallPortFilter
# cmdlet where this parameter is a string array (String[])
Protocol = 'TCP,UDP'
}
# now iterate over the $machines hashtable, fill in the two missing parameters in the hash and execute
$machines.GetEnumerator() | ForEach-Object {
$CimSession = Get-CimSession -ComputerName $_.Key
# fill in the two remaining parameters with the IP address and computer name
$ruleParams['LocalAddress'] = $_.Value # IP Address
$ruleParams['CimSession'] = $CimSession
# execute using the ruleParams splat
Write-Host "Creating IPsecRule on computer $($_.Key) with IP address $($_.Value)"
New-NetIPsecRule #ruleParams
$CimSession | Remove-CimSession
}
Disclaimer.. I cannot try this myself, and as I'm not sure the Protocol parameter should be a single comma separated string or a string array, please try this on a limited test set of machines first.
P.S. When creating the $machines hashtable, change this line
$IpAddress = [System.Net.Dns]::GetHostAddresses($Workstation) |
Where-Object { $_.AddressFamily -eq 'InterNetwork' } | select IpAddressToString
into
$IpAddress = [System.Net.Dns]::GetHostAddresses($Workstation) |
Where-Object { $_.AddressFamily -eq 'InterNetwork' } | Select-Object -ExpandProperty IpAddressToString

Trouble executing powershell script on multiple remote machines

I need to generate a list of all users on our network who are members of their workstation's local administrators group. I found a script here https://gallery.technet.microsoft.com/scriptcenter/List-local-group-members-762b48c5#content which was written to list local group members by executing a WMI query through Powershell. I've tested this script and it works well, but I've been trying to modify it to take in a list of computers to check and that's where I've run into trouble. Here's what I've done:
function LocalAdmins
{
param([string]$GroupName = "Administrators")
begin
{
# Get all workstations listed in this text file
$WorkStations = Get-Content -Path C:\useful_lists\testLocal.txt
# Initialize an array to hold the results of the query
$arr = #()
# hash table for storing computer name, member pairings
$hash = #();
}
process
{
foreach ($machine in $WorkStations)
{
$wmi = Get-WmiObject -ComputerName $machine -Query `
"SELECT * FROM Win32_GroupUser WHERE GroupComponent=`"Win32_Group.Domain='$machine',Name='$GroupName'`""
# Parse out the username from each result and append it to the array
if ($wmi -ne $null)
{
foreach($item in $wmi)
{
$arr += ($item.PartComponent.Substring($item.PartComponent.IndexOf(',') + 1).Replace('Name=', '').Replace("`"", ''))
}
}
# Return a hash table comprised of two columns: Computer Name & Members
$hash += #{ComputerName=$machine;Members=$arr}
}
}
end
{
return $hash
}
}
When I ran the unmodified script here's what I got as output:
PS > (Get-LocalGroupMembers -ComputerName "<COMPUTER NAME>" -GroupName "Administrators").Members
ACCOUNTNAME
ACCOUNTNAME
ACCOUNTNAME
PS >
However, when I run the version of this script that I modified I get this:
PS > (LocalAdmins -GroupName "Administrators").Members
PS >
I'm fairly certain that the issue lies either in how I've setup the first foreach loop to run the wmi query or how the results of that query are being stored in the hash table. I'm not sure what I could do differently to fix the issue.
Thanks in advance to anyone who can help!
UPDATE
Per mortenya's suggestion, I edited my test text file to only include one computer in it. Doing so, along with taking out the foreach ($machine in $computers) loop worked as expected producing the following result:
>> LocalAdmins -GroupName "Administrators"
Name Value
---- ----
ComputerName {computerName.domain}
Members {account, account, account, account}
>>
However, going back and trying to get this to work when incorporating multiple machines using the code above (I've updated it since my initial post), I get the following:
>> LocalAdmins -GroupName "Administrators"
Name Value
---- -----
ComputerName computerName1.domain
Members {}
ComputerName computerName2.domain
Members {}
>>
Why is it that with one machine in the list I can get the members of the Administrator group, but adding a second computer to the list makes it so I can not retrieve members from that group on either machine?
So, if you're going to use Begin{}, Process{}, and End{}, use them for what they're meant for, in the Begin{} block, initialize all your arrays and constant varaibles.
Begin {
# Get all workstations listed in this text file
$WorkStations = Get-Content -Path C:\useful_lists\testLocal.txt
# Store the contents of that list in an array
$computers = #()
$hash = #()
}
Outside of that, I did this same thing a few months ago, it's a little messy, but it spit out a list of computers and who was in the Local Administrators group. It was partially to practice some different methods.
$output = 'c:\psresults\ListOfLocalAdministratorsGroup.txt'
$results = New-Object System.Collections.ArrayList
$objSID = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544")
$objgroup = $objSID.Translate( [System.Security.Principal.NTAccount])
$objgroupname = ($objgroup.Value).Split("\")[1]
foreach($server in (Get-ADComputer -Filter *).name)
{
$admins = New-Object System.Collections.ArrayList
$group =[ADSI]"WinNT://$server/$objgroupname"
$members = #($group.psbase.Invoke("Members"))
$members | foreach {
$obj = new-object psobject -Property #{
Server = $Server
Admin = $_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)
}
#$obj
$admins.Add($obj)
}
$results.Add($admins)
}
$results | Out-File $Output
I found the meat of that somewhere and then modified it a bit.
EDIT: I just put this into ISE and it seems to work fine
$machine = "testsrv"
$groupname = "Administrators"
$wmi = Get-WmiObject -ComputerName $machine -Query `
"SELECT * FROM Win32_GroupUser WHERE GroupComponent=`"Win32_Group.Domain='$machine',Name='$GroupName'`""
if ($wmi -ne $null)
{
foreach ($item in $wmi)
{
$arr += ($item.PartComponent.Substring($item.PartComponent.IndexOf(',') + 1).Replace('Name=', '').Replace("`"", ''))
}
}
$hash = #{ComputerName=$machine;Members=$arr}
return $hash
Get it working on one machine, then start trying to add the loops back in.
EDIT 2.0:
I made a .txt file with only computer names in it, not the FQDN, that works fine for me. I can run it and get results using your script with minor modification.
Despite what I'd said about the Begin{} block, the $arr variable will need to be initialized inside the foreach ($machine in $WorkStations) loop. The reason for this is that when the loop runs, it will create the $arr array, add the data we want, insert that data into a global variable, and then clean up the $arr variable. If we make this global, it won't be cleaned up until the function is done, and we will just keep adding to it, which isn't what we actually want in this case.
The problem you're having with getting multiple machines to work is likely how you're building your results table.

Adding members to local groups by SID in multiple languages

I'm new to powershell/scripting/life in general, but finally I got a problem that is worthy of asking for help:
I've various Windows localizations in environment - English, Finnish and Russian in current environment, but with possibilities to have other Scandinavian/European localizations. I need to add Authenticated users to Administrators group. I can script it in English:
NET LOCALGROUP Administrators "Authenticated Users" /add,
but I won't know all localized names. For example, in Russian it would be "Administratori" and "Proshedshie Proverku." In cyrilic, that I'm not that strong with anyway.
Of course, I know SIDs - S-1-5-32-544 for Administrators and S-1-5-11 for Authenticated users. However, running
NET LOCALGROUP S-1-5-32-544 S-1-5-11 /add returns error that group doesn't exist. Ok, so I found a script to check it -
$objUser = New-Object System.Security.Principal.NTAccount("kenmyer")
$strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier])
$strSID.Value
This returns expected value, so far so good. Then I tried to double check it -by running line to get name from SID -
$Admin = (Get-WMIObject -Class Win32_Group -Filter "LocalAccount=True and SID='S-1-5-32-544'").Name
$Auth = (Get-WMIObject -Class Win32_Group -Filter "LocalAccount=True and SID='S-1-5-11'").Name
And $Admin = Administratori (as it should be), while $Auth = nothing. There is no name. And that's where I stopped. I tried this in English environment as well - still got "no such group" message. Running first command I wrote, with both names in English - Works perfectly Ok.
Any ideas?
Upd:
Perhaps I can't explain properly what I'm trying to do, so let the script do the talking:
#Task: to add "Authenticated users" to "Administrators" group in any languange OS.
$objSID = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544")
$objgroup = $objSID.Translate( [System.Security.Principal.NTAccount])
$objgroupnameAdm = ($objgroup.Value).Split("\")[1]
$objSID = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-11")
$objgroup = $objSID.Translate( [System.Security.Principal.NTAccount])
$objgroupnameAuth = ($objgroup.Value).Split("\")[1]
#Administratörer
#Autentiserade användare
net localgroup $objgroupnameAdm $objgroupnameAuth /add
I try this on Swedish Win7 right now. So result is:
net.exe : Syntaxen för kommandot är:
At line:13 char:4
+ net <<<< localgroup $objgroupnameAdm $objgroupnameAuth /add
+ CategoryInfo : NotSpecified: (Syntaxen för kommandot är::String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
I've tried also putting $objgroupnameAuth in quotes, as it contains two words, but that gives same result. Definig variables as string - no changes, and replacing $objGroupNameAdm with actual value - no changes.
If I couldn't do it in English Windows, I would assume that it's just functionally impossible.
I use this method to translate from SID to localized name:
.SYNOPSIS
Adds the "NT AUTHORITY\Interactive security principal to the local computer Administrators group"
.DESCRIPTION
THis script uses a SID translation to receive the localized name for the Interactive principal and the Administrators group, then adds the principal to the group using the localized names.
# Translate the S-1-5-32-544 (.\Administrators) SID to a group name, the name varies depending on the language version of Windows.
$sid2 = 'S-1-5-32-544'
$objSID2 = New-Object System.Security.Principal.SecurityIdentifier($sid2)
$localadminsgroup = (( $objSID2.Translate([System.Security.Principal.NTAccount]) ).Value).Split("\")[1]
# Translate the S-1-5-4 (NT AUTHORITY\Interactive) SID to an account name, the name varies depending on the language version of Windows.
$sid1 = 'S-1-5-4'
$objSID1 = New-Object System.Security.Principal.SecurityIdentifier($sid1)
$interactive = (( $objSID1.Translate([System.Security.Principal.NTAccount]) ).Value).Split("\")[1]
# Add the security principal name to the local administrators group. (used old style of adding group members due to compatibility reasons)
try {
Write-Host "Adding security principal: $interactive to the $localadminsgroup group..."
$group = [ADSI]"WinNT://$env:computername/$localadminsgroup,group"
$ismember = "False"
#($group.Invoke("Members")) | ForEach-Object {
If ($interactive -match $_.GetType.Invoke().InvokeMember("Name", 'GetProperty', $null, $_, $null)) {
$ismember = "True"
}
}
If ($ismember -eq "True") {
write-host "user $interactive is already a member of $localadminsgroup"
}
Else {
$result = $group.Add("WinNT://NT AUTHORITY/$interactive,user")
write-host "user $interactive is added to $localadminsgroup"
}
}
Catch {
write-host $_.Exception.Message
}
It's not entirely tailored towards what you need but I'm sure you can make it work.
Regards,
Koen.
The SID S-1-5-11 is used for Authenticated Users (Well known SIDs). That is a BUILTIN group that cannot be modified. Other groups like this are Everyone, or Anonymous, etc.
This type of group doesn't exist in the "physical" sense or the word, i.e. there is no object created either in the local SAM nor in the Active Directory.
They are entirely generated and managed by Windows.
You receive the SID in your session depending on how you connected and/or logged on.
Therefore making a WMI Win32_Group request, or using Get-ADGroup won't return anything.
You can invoke Get-ADAccountAuthorizationGroup, to see if a particular identity is member of such groups.
You can use the SID to retrieve create a create a System.Security.Principal.NTAccount object pointing to Authenticated Users:
$auth = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-11")
$name = $auth.Translate([System.Security.Principal.NTAccount])
UPDATE:
I could not add the localized name of $auth to that group. Looks like only the English version works.
Question is simple, once asked correctly, and answer is to be found here: Microsoft Supprt: NET /ADD command .
If NET limit is 20 characters, and "Autentiserade användare" is 24 characters, it's not supposed to work. Workaround is to be found in the same link.

observablecollection predicate filter

I would like to know how to filter the collection $view using a predicate filter. I have seen numerous examples of a predicate filter in C# but not many for PowerShell. It would be great to see a working example in PowerShell.
Basically when I set the filter variable to a string and refresh $view, the collection should filter to show me just the rows or objects that matched the filter. If the filter is empty the entire collection of objects should be shown.
I think this should work in the console without using any forms but i haven't been able to create a filter of the type (system.predicate) in PowerShell.
$a = New-Object System.Collections.ObjectModel.ObservableCollection[object]
$svcs = gsv -ComputerName LocalHost |
select #{n="Server";e={$_.machinename}},Name,Displayname,status
$svcs | ForEach {
$a.Add((
New-Object PSObject -Property #{
Server = $_.server
Name = $_.name
Displayname = $_.displayname
Status = $_.status
}
))
}
$view = [System.Windows.Data.CollectionViewSource]::GetDefaultView($a)
$filter = "bits"
$view.Filter = "Predicate FIlter???"
$view.Refresh()
Just pass a script block which takes a single parameter. The script block should return true for items which you want to include in the view and false for those that you do not want to include. The following works for me on PowerShell v4:
$view = [System.Windows.Data.CollectionViewSource]::GetDefaultView($a)
Write-Host "Setting filter to 'vss'"
$filter = "vss"
$view.Filter = {param ($item) $item -match $filter}
$view.Refresh()
$view
Write-Host "Setting filter to 'BITS'"
$filter = "BITS"
$view.Refresh()
$view
EDIT: Adding the output printed on a test computer of mine
Running the above script on a test computer resulted in the following output:
Setting filter to 'vss'
Status Server Name Displayname
------ ------ ---- -----------
Running LocalHost SQLWriter SQL Server VSS Writer
Stopped LocalHost vmicvss Hyper-V Volume Shado...
Stopped LocalHost VSS Volume Shadow Copy
Setting filter to 'BITS'
Running LocalHost BITS Background Intellige...