MiMenl's Posts

hi Zhi,   A late reply, in 7.13.1.. is seems that sound works ok, atleast trough client when it comes to store the sound level properly when disconnecting en reconnecting. (no  boost to 100 %) als... See more...
hi Zhi,   A late reply, in 7.13.1.. is seems that sound works ok, atleast trough client when it comes to store the sound level properly when disconnecting en reconnecting. (no  boost to 100 %) also the mute status is properly handled (it used to unmute) As Robin mentioned we are not yet at horizon 8. once we are I'll also update this post once we are.    
This is not really my cup of tea. According to your story, it works on domain joined machines which are probably all trusted and i suspect there is no real internal fire walling going on internally.... See more...
This is not really my cup of tea. According to your story, it works on domain joined machines which are probably all trusted and i suspect there is no real internal fire walling going on internally. also you are probably directly connetiong ovet internal adresses and not through the outside facing IP for internal connections. in this case HTML is working but this might be because HTML is run from the connection server if I am corerrect which can be exposed to the ouside and kinda uses the normal https ports. when using is client a couple of things get important. Which diplay protocol will you be using blast > ? in that case other ports are required where most is TCP  based iIthink blast also has an UDP part, Then there isthe tunneling part. are you using your onnection server in tunnel mode or not, did you implement UAG's ? . this will change the behaviour too. cause reading your story only the external part is configured towards the connection server an not the endpoint side which might cause a peer to peer connection to fail.   this might be a difference from within the domain ( or your test environment) were alle is/was probably routed and able to reach eachother. maybe take a look at : https://docs.vmware.com/en/VMware-Horizon-7/7.13/horizon-installation/GUID-AF394B4F-B36A-4C32-9FFA-663ECD5B0968.html in case you did not do his already. I'm not too  much into networking but this is what I would take a look at.          
Hi i Just wanted to share this in case it can be of any help to someone. Its a script (with GUI) which can be used to send messages to connected users in a pod. The script uses the local instance s... See more...
Hi i Just wanted to share this in case it can be of any help to someone. Its a script (with GUI) which can be used to send messages to connected users in a pod. The script uses the local instance so you need to connect to the connection server which belongs to the pod you want to send a message too. It is only possible to sens a message desktop pool based. I could not find a proper space to add this to teh vemware code part of the forum. but feel free to move this if this is not the right location. #Powershell script with GUI to send messages to selected users in an horizon environment. #The HTML 5 interface in horizon 7.x can only send messaged to 20 people at once where flash was able to send many more. #This will probably be changed in a later release of horizon. #This script was made to overcome this issue in case you need to send a message to more pople at once. #It uses the local instance so connect so the connection server of the pod containing the pool you want to send a message too. #The Script comes without any warranty. #Mark Platte 2021 Add-Type -assembly System.Windows.Forms [System.Windows.Forms.Application]::EnableVisualStyles() #variable setup $strUser = "" $strServer = "" $strPassword = "" $strdomain = "" $script:hvServer $script:hvServices $script:queryResultsService #form Setup #form $main_form = New-Object System.Windows.Forms.Form $main_form.Text ='Horizon send Message ®Mark Platte 2021' $main_form.Width = 800 $main_form.Height = 810 $main_form.ControlBox =$false $main_form.StartPosition="CenterScreen" $main_form.FormBorderStyle = "FixedSingle" #button connect $btnButtonConnect = New-Object System.Windows.Forms.Button $btnButtonConnect.Location = New-Object System.Drawing.Size(0,120) $btnButtonConnect.Size = New-Object System.Drawing.Size(780,25) $btnButtonConnect.Text = "Connect to connection server" $main_form.Controls.Add($btnButtonConnect) #button exit $btnExit = New-Object System.Windows.Forms.Button $btnExit.Location = New-Object System.Drawing.Size(620,740) $btnExit.Size = New-Object System.Drawing.Size(150,25) $btnExit.Text = "Exit" $main_form.Controls.Add($btnExit) #button sendmessage $btnSend = New-Object System.Windows.Forms.Button $btnSend.Location = New-Object System.Drawing.Size(520,475) $btnSend.Size = New-Object System.Drawing.Size(250,25) $btnSend.Text = "Send Message(s)" $main_form.Controls.Add($btnSend) #button Select all $btnSelect = New-Object System.Windows.Forms.Button $btnSelect.Location = New-Object System.Drawing.Size(48,500) $btnSelect.Size = New-Object System.Drawing.Size(100,25) $btnSelect.Text = "Select all" $main_form.Controls.Add($btnSelect) #button deselectall $btndeSelect = New-Object System.Windows.Forms.Button $btndeSelect.Location = New-Object System.Drawing.Size(148,500) $btndeSelect.Size = New-Object System.Drawing.Size(100,25) $btndeSelect.Text = "Deselect all" $main_form.Controls.Add($btndeSelect) #button Ger users $btnGetUsers = New-Object System.Windows.Forms.Button $btnGetUsers.Location = New-Object System.Drawing.Size(525,158) $btnGetUsers.Size = New-Object System.Drawing.Size(250,25) $btnGetUsers.Text = "Get Connected Users" $main_form.Controls.Add($btnGetUsers) #labels $lblServer = New-Object System.Windows.Forms.Label $lblServer.Location = New-Object System.Drawing.Size(0,15) $lblServer.Size = New-Object System.Drawing.Size(150,25) $lblServer.Text = "Connection Server:" $main_form.Controls.Add($lblServer) $lblUser = New-Object System.Windows.Forms.Label $lblUser.Location = New-Object System.Drawing.Size(0,40) $lblUser.Size = New-Object System.Drawing.Size(150,25) $lblUser.Text = "User Name:" $main_form.Controls.Add($lblUser) $lblPassword = New-Object System.Windows.Forms.Label $lblPassword.Location = New-Object System.Drawing.Size(0,65) $lblPassword.Size = New-Object System.Drawing.Size(150,25) $lblPassword.Text = "Password:" $main_form.Controls.Add($lblPassword) $lbldomain = New-Object System.Windows.Forms.Label $lbldomain.Location = New-Object System.Drawing.Size(0,90) $lbldomain.Size = New-Object System.Drawing.Size(150,25) $lbldomain.Text = "Domain:" $main_form.Controls.Add($lbldomain) $lblPool = New-Object System.Windows.Forms.Label $lblPool.Location = New-Object System.Drawing.Size(0,165) $lblPool.Size = New-Object System.Drawing.Size(75,25) $lblPool.Text = "Select pool: " $main_form.Controls.Add($lblPool) $lblStatus = New-Object System.Windows.Forms.Label $lblStatus.Location = New-Object System.Drawing.Size(50,740) $lblStatus.Size = New-Object System.Drawing.Size(50,25) $lblStatus.Text = "Status: " $main_form.Controls.Add($lblStatus) $lblMessage = New-Object System.Windows.Forms.Label $lblMessage.Location = New-Object System.Drawing.Size(100,740) $lblMessage.Size = New-Object System.Drawing.Size(700,25) $lblMessage.Text = "Please fill in the requested information to connect" $main_form.Controls.Add($lblMessage) $lblPriority = New-Object System.Windows.Forms.Label $lblPriority.Location = New-Object System.Drawing.Size(520,455) $lblPriority.Size = New-Object System.Drawing.Size(55,25) $lblPriority.Text = "Sevirity:" $main_form.Controls.Add($lblPriority) #textboxes $txtServer = New-Object System.Windows.Forms.TextBox $txtServer.Location = New-Object System.Drawing.Size(150,10) $txtServer.Size = New-Object System.Drawing.Size(400,25) $txtServer.Text = "" $main_form.Controls.Add($txtServer) $txtUser = New-Object System.Windows.Forms.TextBox $txtUser.Location = New-Object System.Drawing.Size(150,35) $txtUser.Size = New-Object System.Drawing.Size(400,25) $txtUser.Text = "" $main_form.Controls.Add($txtUser) $txtPass = New-Object System.Windows.Forms.TextBox $txtPass.Location = New-Object System.Drawing.Size(150,60) $txtPass.Size = New-Object System.Drawing.Size(400,25) $txtPass.Text = "" $txtPass.PasswordChar ="*" $main_form.Controls.Add($txtPass) $txtdomain = New-Object System.Windows.Forms.TextBox $txtdomain.Location = New-Object System.Drawing.Size(150,85) $txtdomain.Size = New-Object System.Drawing.Size(400,25) $txtdomain.Text = "" $main_form.Controls.Add($txtdomain) $txtMessage = New-Object System.Windows.Forms.TextBox $txtMessage.Multiline =$true $txtMessage.Location = New-Object System.Drawing.Size(520,200) $txtMessage.Size = New-Object System.Drawing.Size(250,240) $txtMessage.Text = "" $main_form.Controls.Add($txtMessage) $txtstatus = New-Object System.Windows.Forms.TextBox $txtstatus.Location = New-Object System.Drawing.Size(50,530) $txtstatus.Size = New-Object System.Drawing.Size(725,195) $txtstatus.Multiline =$true $txtstatus.ScrollBars="Vertical" $txtstatus.Text = "" $main_form.Controls.Add($txtstatus) #comboboxes $cbPools = New-Object System.Windows.Forms.ComboBox $cbPools.Location = New-Object System.Drawing.Size(80,160) $cbPools.Size = New-Object System.Drawing.Size(150,25) $main_form.Controls.Add($cbPools) $cbPriority = New-Object System.Windows.Forms.ComboBox $cbPriority.Location = New-Object System.Drawing.Size(575,450) $cbPriority.Size = New-Object System.Drawing.Size(195,25) $cbPriority.Items.Add("WARNING") $cbPriority.Items.Add("ERROR") $cbPriority.Items.Add("INFO") $cbPriority.SelectedIndex =2 $main_form.Controls.Add($cbPriority) #ListView $dvUsers = New-Object System.Windows.Forms.ListView $dvUsers.Location = New-Object System.Drawing.Size(50,200) $dvUsers.Size = New-Object System.Drawing.Size(450,300) $dvUsers.MultiSelect =$true $dvusers.CheckBoxes =$true $dvUsers.View ="Details" $dvUsers.GridLines =$true $dvusers.Columns.Add("User").Width =150 $dvusers.Columns.Add("Session State").Width =150 $dvusers.Columns.Add("Desktop Pool").Width =150 $dvusers.Columns.Add("id").Width =150 $main_form.Controls.Add($dvUsers) #Checkboxes $checkbox1 = new-object System.Windows.Forms.checkbox $checkbox1.Location = new-object System.Drawing.Size(250,145) $checkbox1.Size = new-object System.Drawing.Size(250,50) $checkbox1.Text = "Only get Conneted sessions" $checkbox1.Checked = $true $main_form.Controls.Add($checkbox1) #functions #fucntion to conenct to 1 connection server function Connect-Horizon { try{ $lblMessage.text = "Trying to connect to " + $strServer.ToString() Connect-HVServer -Server $strServer -User $strUser -Password $strPassword -Domain $strdomain $lblMessage.text = "Connected to " + $strServer.ToString() } catch{$lblMessage.Text = "Error While connecting please check provided information "} } function Write-text($string) { $txtstatus.Text = $txtstatus.text + $string + "`r`n" } #eventhandling #btnConnect handler #Click will get details form textfields to getr connection information (its not all secure no secure string is used so be sureto close program after use) #it will also populate the pool combobox. $btnButtonConnect.Add_Click( { $cbPools.Items.Clear() $strServer = $txtServer.Text.ToString() $strUser =$txtUser.Text.ToString() $strPassword = $txtPass.Text.ToString() $strdomain = $txtdomain.Text.ToString() if( $strServer -ne "" -and $strUser -ne "" -and $strPassword -ne "" ) { $script:hvServer = Connect-Horizon $script:hvServices = $hvServer.ExtensionData try{ $queryServiceDesktop = New-Object VMware.Hv.QueryServiceService $defn = New-Object VMware.Hv.QueryDefinition $defn.queryEntityType = 'DesktopSummaryView' $defn.sortBy = 'desktopSummaryData.name' $defn.sortDescending = $true $queryResultsDesktop = $queryServiceDesktop.QueryService_Create($script:hvServices, $defn) try{ foreach ($DTresult in $queryResultsDesktop.Results) { $cbPools.Items.Add( $DTresult.DesktopSummaryData.Name.ToString()) } $cbpools.SelectedIndex = 0 } catch {Write-text "An error occured while retrieving data"} } catch{ Write-text "An Error Occured" } } else {$lblMessage.text = "Provide all data"} } ) # click will query user sessions from connection server for the selected pool eithe rall sessions or only connected sessions based on checkbox $btnGetUsers.Add_Click( { $queryServiceService = New-Object VMware.Hv.QueryServiceService $defnService = New-Object VMware.Hv.QueryDefinition $defnService.queryEntityType = 'SessionLocalSummaryView' $defnService.sortDescending = $False $script:queryResultsService = $queryServiceService.QueryService_query($script:hvServices, $defnService) $dvUsers.Items.Clear() try { $lblMessage.text = "Getting Users" $nUsers = 0 foreach ($result in $script:queryResultsService.Results) { if($checkbox1.CheckState -eq "Checked") { if($result.NamesData.DesktopPoolCN.ToString() -eq $cbPools.SelectedItem.ToString().ToLower() -and $result.SessionData.SessionState.ToString() -eq "CONNECTED") { $nUsers = $nUsers + 1 $lvData = New-Object System.Windows.Forms.ListViewItem $lvData.Text = $result.NamesData.UserName.ToString() $lvData.SubItems.Add($result.SessionData.SessionState.ToString()) $lvData.SubItems.Add($result.NamesData.DesktopPoolCN.ToString()) $lvData.SubItems.Add($result.Id.tostring()) $dvUsers.items.Add($lvData) } } if($checkbox1.CheckState -eq "Unchecked") { if($result.NamesData.DesktopPoolCN.ToString() -eq $cbPools.SelectedItem.ToString().ToLower() ) { $nUsers = $nUsers + 1 $lvData = New-Object System.Windows.Forms.ListViewItem $lvData.Text = $result.NamesData.UserName.ToString() $lvData.SubItems.Add($result.SessionData.SessionState.ToString()) $lvData.SubItems.Add($result.NamesData.DesktopPoolCN.ToString()) $lvData.SubItems.Add($result.Id.tostring()) $dvUsers.items.Add($lvData) } } $lblMessage.text = "Retrieved " + $nUsers.ToString() + " sessions" } Write-text $lblMessage.text = "Retrieved " + $nUsers.ToString() + " sessions" } catch {} } ) #Click will send message to all selected users (it will loop trough query results so its not entirly optimal but I did not spend time in saving the ID to an object for later use. $btnSend.Add_Click( { $nerror=0 $nsend=0 foreach($item in $dvUsers.CheckedItems) { foreach($result in $script:queryResultsService.Results) { if($result.NamesData.UserName.ToString() -eq $item.subitems[0].text) { $user = $result.NamesData.UserName.ToString() try{ $hvServices.Session.Session_SendMessage($result.Id,$cbPriority.SelectedItem.ToString(),$txtMessage.Text.ToString()) $nsend = $nsend + 1 Write-text "Message send to : $user" } catch { $nerror = $nerror + 1 Write-text "Error While sending Message to : $user" } } } } $ntotal = $nerror + $nsend Write-text "Tried to send $ntotal message(s) | $nerror Errors | $nsend Send" } ) $btnSelect.Add_Click( { foreach($item in $dvUsers.Items){ $Item.checked =$true } } ) $btndeSelect.Add_Click( { foreach($item in $dvUsers.Items){ $Item.checked =$false } } ) #$hvServices.Session.Session_SendMessage($result.Id,"INFO","test vanuit powershell") #btnExit handler $btnExit.Add_Click( { try{ Disconnect-HVServer $txtServer.Text.ToString() -Force -Confirm $true } catch{write-host "Could not disconnect"} $main_form.Close() } ) Write-text("Program started ") $main_form.ShowDialog()   Hope it helps.   Mark
Just a small remark here. What is being stored in the Program files folder? Is it just 1 single file or are there multiple files. Does the end user change the file or do you just need to pro... See more...
Just a small remark here. What is being stored in the Program files folder? Is it just 1 single file or are there multiple files. Does the end user change the file or do you just need to provide a different file(s) based on application usage (settings .ini etc). It actually doesn't matter much but if it's just one file and you are into some programming it's way easier to achieve than multiple files. In our environment we have one application which needs a setting file that is stored in a folder within the application folder in the program files directory. This file cannot be user bound but has to switch based on the users function throughout the day, the application was usually machine bound of which the location determined the setting file and the function, but with the VDI this location based installation is no longer used. Although by tweaking a bit and leveraging UEM elevated permissions we are able to add the right file to the program files folder when needed. You do need to program for this though. I just create launcher or watcher programs in vb.net that could help with this. if I use a launcher program I just cerate a .exe which will start the real program but preforms some actions before the application is launched example: applauncher.exe will launch app1.exe applauncher.exe will first copy a file from the network to the right place in the program files folder and than launch app1.exe once the copy is done and the existence of the file is checked. app1.exe in this case has the right setting file. copying to program files is usually forbidden (rights) but by leveraging elevated permissions for applauncher.exe trough UEM you can work around this. For a watcher/launcher program you could do something like this  (no real code) copy needed files if they exist from user network share based on username. start your program as a new process Dim p() As Process Private Sub CheckIfRunning()   p = Process.GetProcessesByName("myapp")    If p.Count > 0 Then   ' Process is running do nothing    Else    ' Process is not running start copying files      copy files needed to a users network share based on username      Exit watcher    End If End Sub By rechecking this every couple of seconds on a timer you can keep track if the program is still running. By elevating this program trough UEM files can be written to the program files directory. running the watcher program would cost some system resources but the consumption is very low. Hope this helps. It's not a real integrated solution but it saved me a writable volume. If just applications would just use the roaming folder more.... would make things so much easier. Mark
I Think i'll need to do some testing soon, this idea might be something that would really benefit us too. since I still need to get some experience with Log Insight this might be a good use case... See more...
I Think i'll need to do some testing soon, this idea might be something that would really benefit us too. since I still need to get some experience with Log Insight this might be a good use case
Hi, I dont know if this is the right place for this but I think it might come in handy in the context of appvolumes. if it needs to be put somewere else feel free to move it to another sectio... See more...
Hi, I dont know if this is the right place for this but I think it might come in handy in the context of appvolumes. if it needs to be put somewere else feel free to move it to another section of the forum. While trying to automate installations to create appstacks, which I do in powershell I often run into the issue that my arguments are not parsed the right way, this usually happens when I need to combine a lot of argumenst with the -ArgumentList option in powershell. Since it sometimes is hard to track what actually happens I wrote a small program which can be used to replace the executable you want to install. The program wil show you the way the arguments are fed to the executable. Its a very small and actually very easy program to make but it really helps me so it might also help others. I'll add the source to this post, and will explain the code a bit too within the code with some remarks. Public Class Form1     Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load         Dim commandlimeargs As String() = Nothing         'get commandline parameters         commandlimeargs = Environment.GetCommandLineArgs()         For i = 0 To commandlimeargs.Length - 1             'create nice numbering of parameters to keep stuff alinged             If i < 10 Then                 TextBox1.Text = TextBox1.Text & "Parameter 0" & i & " = " & commandlimeargs(i).ToString & vbCrLf             Else                 TextBox1.Text = TextBox1.Text & "Parameter " & i & " = " & commandlimeargs(i).ToString & vbCrLf             End If         Next         'set cursor at the end of the texttbox         TextBox1.SelectionStart = TextBox1.Text.Length     End Sub     Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click         Me.Close()     End Sub     Private Sub AboutToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles AboutToolStripMenuItem.Click         'show teh about form         frmAbout.ShowDialog()     End Sub     Private Sub Form1_Resize(sender As Object, e As EventArgs) Handles Me.Resize         'some resizing logic         TextBox1.Width = Me.Width - 40         Button1.Left = Me.Width - Button1.Width - 28         TextBox1.Height = Me.Height - 100         Button1.Top = Me.Height - 65     End Sub End Class The program in action with some bogus parameters Execution string : Start-Process -PassThru -Wait -FilePath "c:\cmdtest.exe" -ArgumentList"/admin","/a","/I","/L*","`"D:\!TestMap\Log\CMDtest.log`"","/n","/q","/s","/v","/Passive","/norestart","REBOOT=ReallySuppress","/qn","/test bla /reboot=True","/adminfile","`"D:\!TestMap\Software Root\Transform_File.MSP`"","/config","`"D:\!TestMap\Software Root\Config_File.XML`"" In powershell : Result : In this case you can see the first argument is not right since -argumentlist/admin should have been split this happend because I forgot a space between -ArgumentList"/admin" The right string would be : Start-Process -PassThru -Wait -FilePath "c:\cmdtest.exe" -ArgumentList "/admin","/a","/I","/L*","`"D:\!TestMap\Log\CMDtest.log`"","/n","/q","/s","/v","/Passive","/norestart","REBOOT=ReallySuppress","/qn","/test bla /reboot=True","/adminfile","`"D:\!TestMap\Software Root\Transform_File.MSP`"","/config","`"D:\!TestMap\Software Root\Config_File.XML`"" This will give this result : Every parameter on a separate line which is the right way. It really saved me quite some time debugging my scripts hope it helps others too. I also started a project to save and generate powershellscripts trough gui for easy installs, but never got to finishing it, I might start working on that one again too if there is any interest, maybe even start a blog about it so let me know if you think it might be usefull. Mark
This sounds helpful, although it would be nice nice if the human readable log file could contain 2 more fields separated by a space or something before the whole message is presented. if we ... See more...
This sounds helpful, although it would be nice nice if the human readable log file could contain 2 more fields separated by a space or something before the whole message is presented. if we look at the following log entries 2020-06-30 17:35:29.319 [INFO ] Completed DirectFlex export (150 ms) [<<IFP#4aaaa44b-1148c24] 2020-06-30 17:35:31.146 [INFO ] Performing DirectFlex export for config file '\\domain\uem\config\general\Applications\App.INI' [IFP#1a07110c-1148c24>>] 2020-06-30 16:55:35.124 [FATAL] Error backing up '\\domain\uem\users\m.platte\archives\Windows Settings\Windows 10 Start-Tile Screen.zip' to '\\domain\uem\users\m.platte\Backup\Windows Settings\Windows 10 Start-Tile Screen 2020-06-30 999999.zip' (2) 2020-06-30 16:55:35.575 [WARN ] Needed 4 retries to rename profile archive from '\\domian\uem\users\m.platte\archives\Windows Settings\Windows 10 Start-Tile Screen.tmp' to '\\domain\uem\users\m.platte\archives\Windows Settings\Windows 10 Start-Tile Screen.zip' 2020-05-01 12:25:19.590 [ERROR] Async UEM actions or UEM Refresh in progress filtering out fields could be done like this (my regex sucks) : so this might not work as intended I couldn't test it properly \d{4}-\d{2}-\d{2} \d{1,2}:\d{1,2}:\d{1,2}.\d{1,4} for date and time stamp \[[A-Z]{4,8}.\] for extracting Label (ERROR, FATAL, INFO , WARN) \(\d*\sms\) to extract the time when available (\[\<\<.*\]|\[IFP.*\>\>\]) To get out the message correlation (?<=\]).* to capture the whole message Based on this you should be able to extract fields from the human readable log By getting the time and correlation out of the message like this : 2020-06-30 17:35:29.319 [INFO ] [150 ms] [<<IFP#4aaaa44b-1148c24] Completed DirectFlex export 2020-06-30 17:35:31.146 [INFO ] [0 ms] [IFP#1a07110c-1148c24>>] Performing DirectFlex export for config file '\\domain\uem\config\general\Applications\App.INI' It would become way easier to filter due to the fields always being in teh same order  when there is no correlation it might be handy to just add [ ] without data. 2020-05-01 13:12:14.775 [INFO ] [0ms] [ ] Importing profile archive 'Profit Communication Center - AS.zip' (\\domain\uem\users\m.platte\archives\Applications\app - AS.zip) According to event forwarding I think Elastic en splunk have forwarder apps that can forward event logs for you. In this case i would setup a windows log collector let your machines forward logs to your log collector (new entries) Install a universal forwarder on your log collector (splunk/elastic (Win log beat as sjesse mentioned /log insight agent) and let it forward teh events to your indexer. seems working on my regex actually let me miss 2 posts :smileysilly: Hope this helps. Mark
Just wanted to share a link to a topic I made on the DEM part of the forum. It might also come in handy for horizon, so I just link to it from here The “not so“ nice things about sound and Ho... See more...
Just wanted to share a link to a topic I made on the DEM part of the forum. It might also come in handy for horizon, so I just link to it from here The “not so“ nice things about sound and Horizon in a healthcare environment. Mark
Hi Pieter, The log files can be stored centralized on a network share. The log files will be stored on a per user bases I like the idea you have and might take a look at it cause it might ... See more...
Hi Pieter, The log files can be stored centralized on a network share. The log files will be stored on a per user bases I like the idea you have and might take a look at it cause it might be very handy. Just a wild idea here : It seems the logfiles are all called the same for each users so when indexing with tools like log inside or splunk you would need to create a custom script that for example appends the username somewhere when indexing the files, this part needs some thinking cause you would need to create some sort of flow that doesn't cause duplicate events. Then when it comes to indexing it would require some regex to substract the information in such a way it could be used. If I  look at the log files they are all build in a similar way : [Timestamp] [Message Type] [Message] 2020-06-30 17:35:29.278 [INFO ] Exported file information successfully 2020-06-30 17:35:29.319 [INFO ] Completed DirectFlex export (150 ms) [<<IFP#4aaaa44b-1148c24] 2020-06-30 17:35:31.146 [INFO ] Performing DirectFlex export for config file '\\domain\uem\config\general\Applications\App.INI' [IFP#1a07110c-1148c24>>] The Timestamp, message type fields should be easy the last field depending on what you want to achieve gets more complicated since it contains a full message were we only want some info from for example the time in ms an action took, because this field contains a lot of different data this gets hard to filter. (maybe we should ask for a csv, Json, XML, or JSON formatted log with some fixed field names) something like : [TimeStamp],[username],[Message Type],[Duration(ms)],[Message] when doing csv (but not the case right now) once you are able to get the data into Log inside or Splunk properly it should be easy to search based on the indexed fields. this would be my approach since Log Inside and Splunk can do some great things when it comes to going trough events fast. it is however always possible to load the data to SQL or something but then you'll need to do a lot more programming. this is not a solution but just an idea how in my opinion something like you asked could be achieved. Mark
Hi Mark, Wow, what a nice post! Thanks for sharing! I have a question, I work also in a large hospital in the south, same country as you. We are going to implement a Horizon environment W... See more...
Hi Mark, Wow, what a nice post! Thanks for sharing! I have a question, I work also in a large hospital in the south, same country as you. We are going to implement a Horizon environment W10 with DEM and App Volumes (2 or 4), within the next 6 months. At the moment we use Citrix XenDesktop, that will be migrated to Horizon. We use around 2000 concurrent VDI's and a lot of fat clients. At the moment we are designing (together with a third party) our environment. Is it possible to make an appointment with you, so we can come to your hospital and see how your environment is set-up, problems, lessons learned, etc. Let me know if that is possible for you. Best regards, Pieter I send you PM, Regards, Mark
Hi Mark, Ok, wow! What an extensive and detailed post! Great work and thanks for sharing! I agree that it is best to have this fixed/adjusted in the product. Have you done a feature reques... See more...
Hi Mark, Ok, wow! What an extensive and detailed post! Great work and thanks for sharing! I agree that it is best to have this fixed/adjusted in the product. Have you done a feature request for this? --> https://www.vmware.com/company/contact/contactus.html?department=prod_request Thanks again! Yeah i could do that , I think it's a known issue and that for now there wasn't much of a solution. I'll take a  look at the page you linked to see I can fill in a feature request, maybe they can also find a way based on event handling from a DEM agent or something. since that service is used a lot in Horizon environments anyway and runs. or maybe even the horizon agent itself.
Part 2 100% sound level reset at reconnect. This was a hard one and actually the one issue that started it all. Trying to find a solution on the internet never got me anywhere. Lots of ... See more...
Part 2 100% sound level reset at reconnect. This was a hard one and actually the one issue that started it all. Trying to find a solution on the internet never got me anywhere. Lots of recommendations of doing something during log on and log off events but no real solution to the reset of the sound levels to 100 % when reconnecting. Even asking around at lots of places never got my any further than learn to live with this cannot be fixed. And that just is an answer I don’t like, and don’t believe (see me introduction  :smileylaugh: ) So yes it was quite some work it involves some 3rth party libraries but in the end I got something that works, only downside due to the way windows manages events it sometimes lags for 1 to 3 seconds before changing the settings but this is always better than not changing them at all. In this case the user should be able to set the volume themselves so I created a vb.net Form application which resides in the system tray to fix the sound issue. The application uses xenolightning / AudioSwitcher (https://github.com/xenolightning/AudioSwitcher/blob/master/LICENSE) as wrapper dll to implement some fdunctions to change the sound settings. These dll’s can be downloaded trough the NuGet function in visual studio : These DLL’s really made things a lot easier. And are under a Microsoft Public License So for the program : What do we have to fix ? Well mainly the fact that when you reconnect to a VDI the sound will reset to 100 % and the settings cannot be saved anywhere, since there is no logon or logoff event a logon or logoff DEM script will not fix this either. But it would be nice if the user preferences could be saved and restored trough DEM so let’s keep that in mind. So what is it I can use to actually know when to change the sound levels, let’s see if there is something triggered when I reconnect back to my VDI. And this seems to be the case. Apparently at least in our environment a reconnect is seen as a session logon or a session unlock two events that can be handled by a VB.net application so let’s try to (ab)use that, and this turned out to become the main part of the program : Adding an event handler AddHandler Microsoft.Win32.SystemEvents.SessionSwitch, AddressOf SessionSwitch And adding a function that triggers Private Sub SessionSwitch(ByVal sender As Object, ByVal e As Microsoft.Win32.SessionSwitchEventArgs) If e.Reason = e.Reason = SessionSwitchReason.SessionLogon Or e.Reason = SessionSwitchReason.SessionUnlock Then Some code to change sound settings and done End if End sub This up here looks easy but after adding some code it just wasn’t working right somehow sound could not be set at once. I read in a post that increasing sound level by 1 and decreasing it again triggers a change with a different NirSoft tool so we had to work from there. And that became the code below. I added some comments (and if you compile it this way you will have a paypal link to me in the about form but feel free to delete it, although it was quite some work getting this stuff working) Since the forms contain some element it might be smart to download the source file since it will be easier to recreate. Setup from scratch : Add two forms to the project (Form1 and Form 2) Here is a screenshot of the controls and names on form 1 and form 2 Add the components to the right forms and take over the right names for the code to work. Now add the code below to Form1 Form1 'imports some needed libraries dont forget to also reference the dlls into your solution Imports System.ComponentModel Imports System.Runtime.InteropServices Imports AudioSwitcher Imports Microsoft.Win32 Public Class Form1     'create a sound device to use     Public device As AudioSwitcher.AudioApi.CoreAudio.CoreAudioDevice     Public strappdata As String     'hide the close button on the form     Private Const CP_NOCLOSE_BUTTON As Integer = &H200     Protected Overloads Overrides ReadOnly Property CreateParams() As CreateParams         'make the close button dissapear         Get             Dim myCp As CreateParams = MyBase.CreateParams             myCp.ClassStyle = myCp.ClassStyle Or CP_NOCLOSE_BUTTON             Return myCp         End Get     End Property     Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load         'add an event handler to catch the logon and unlock events         AddHandler Microsoft.Win32.SystemEvents.SessionSwitch, AddressOf SessionSwitch         'get the appdata folder for teh user         strappdata = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)         'get the default playback device usually the master volume when no other devices are present         device = New AudioApi.CoreAudio.CoreAudioController().DefaultPlaybackDevice         'print info on a label on the form and resize properly         Label1.Text = "Current volume level of: " & device.FullName.ToString         lblvolume.Left = Label1.Left + Label1.Width + 10         HScrollBar1.Left = lblvolume.Left + lblvolume.Width + 10         btnmute.Left = HScrollBar1.Left         Button1.Left = btnmute.Left + btnmute.Width - Button1.Width         Me.Width = HScrollBar1.Left + HScrollBar1.Width + 25         'check for setting file and if its present read values from it to set the master volume         If System.IO.File.Exists(strappdata.ToString & "\SetVolume\Volume.ini") = True Then             Dim sReader As System.IO.StreamReader = Nothing             Dim strRead() As String = Nothing             sReader = New IO.StreamReader(strappdata.ToString & "\SetVolume\Volume.ini")             While sReader.Peek <> -1                 strRead = Split(sReader.ReadLine, "=")                 If strRead(0).ToString = "Volume" Then                     'set volume                     device.Volume = Val(strRead(1).ToString)                 End If                 If strRead(0).ToString = "Mute" Then                     'set mute state                     If strRead(1).ToString.ToUpper = "FALSE" Then                         device.Mute(False)                     End If                     If strRead(1).ToString.ToUpper = "TRUE" Then                         device.Mute(True)                     End If                 End If             End While             sReader.Close()         Else             'create the appdata folder if tit is not there to save ini file and store current volume settings             If System.IO.Directory.Exists(strappdata.ToString & "\SetVolume") = False Then                 System.IO.Directory.CreateDirectory(strappdata.ToString & "\SetVolume")                 System.IO.File.Create(strappdata.ToString & "\SetVolume\Volume.ini").Close()                 Dim sWriter As System.IO.StreamWriter = Nothing                 sWriter = New IO.StreamWriter(strappdata.ToString & "\SetVolume\Volume.ini", False)                 sWriter.WriteLine("Volume=" & device.Volume.ToString)                 sWriter.WriteLine("Mute=" & device.IsMuted.ToString)                 sWriter.Close()             Else                 'store settings in ini file                 System.IO.File.Create(strappdata.ToString & "\SetVolume\Volume.ini").Close()                 Dim sWriter As System.IO.StreamWriter = Nothing                 sWriter = New IO.StreamWriter(strappdata.ToString & "\SetVolume\Volume.ini", False)                 sWriter.WriteLine("Volume=" & device.Volume.ToString)                 sWriter.WriteLine("Mute=" & device.IsMuted.ToString)                 sWriter.Close()             End If         End If         'set lebels on primary form         'change value of scrollbar to show current volume level         lblvolume.Text = device.Volume & " %"         HScrollBar1.Value = device.Volume         If device.IsMuted = True Then             lblmute.Text = "Muted"             btnmute.Text = "Unmute"         Else             lblmute.Text = "Unmuted"             btnmute.Text = "Mute"         End If         'set interval of timer to 30 seconds and let it write the ini file every 30 seconds to preserve changes.         Timer1.Interval = 30000         Timer1.Enabled = True     End Sub     Private Sub SessionSwitch(ByVal sender As Object, ByVal e As Microsoft.Win32.SessionSwitchEventArgs)         'check for event logon or unlock         If e.Reason = e.Reason = SessionSwitchReason.SessionLogon Or e.Reason = SessionSwitchReason.SessionUnlock Then             'diabel timer so settings cannot be written and prevent sound setting mechanism to mess up user settings             Timer1.Enabled = False             'set volume to 1 and to 0             device.Volume = 1             device.Volume = 0             For a = 0 To 10                 'make the device go to mute state by reapeating the setting (it needs to be repeated to work)                 'device will always start in mute state to prevent loud noises.                 device.Mute(False)                 device.Mute(True)                 Threading.Thread.Sleep(5)             Next a             'start reading user settings             Dim sReader As System.IO.StreamReader = Nothing             Dim strRead() As String = Nothing             sReader = New IO.StreamReader(strappdata.ToString & "\SetVolume\Volume.ini")             While sReader.Peek <> -1                 strRead = Split(sReader.ReadLine, "=")                 For i = 0 To 25 'repest settings 25 times                     'check for volume                     If strRead(0).ToString = "Volume" Then                         Threading.Thread.Sleep(5)                         'decrease volume by 1                         device.Volume = Val(strRead(1).ToString) - 1                         Threading.Thread.Sleep(5)                         'set volume to desired level                         device.Volume = Val(strRead(1).ToString)                     End If                     If strRead(0).ToString = "Mute" Then                         'set mute sate                         If strRead(1).ToString.ToUpper = "FALSE" Then                             device.Mute(False)                         End If                         If strRead(1).ToString.ToUpper = "TRUE" Then                             device.Mute(True)                         End If                     End If                 Next i             End While             sReader.Close()         End If         'enable timer to save user changes         Timer1.Enabled = True     End Sub     Protected Overrides Sub Finalize()         MyBase.Finalize()     End Sub     Private Sub btnmute_Click(sender As Object, e As EventArgs) Handles btnmute.Click         'add mute button actions i know GOTO but it's so easy could have used a case instead.         If device.IsMuted = False Then             device.Mute(True)             lblmute.Text = "Muted"             btnmute.Text = "Unmute"             GoTo 1         End If         If device.IsMuted = True Then             device.Mute(False)             lblmute.Text = "Unmuted"             btnmute.Text = "Mute"             GoTo 1         End If 1:         'write new state to ini         Dim sWriter As System.IO.StreamWriter = Nothing         sWriter = New IO.StreamWriter(strappdata.ToString & "\SetVolume\Volume.ini", False)         sWriter.WriteLine("Volume=" & device.Volume.ToString)         sWriter.WriteLine("Mute=" & device.IsMuted.ToString)         sWriter.Close()     End Sub     Private Sub HScrollBar1_ValueChanged(sender As Object, e As EventArgs) Handles HScrollBar1.ValueChanged         'handle scrollbar and settings saving         Try             Dim sWriter As System.IO.StreamWriter = Nothing             sWriter = New IO.StreamWriter(strappdata.ToString & "\SetVolume\Volume.ini", False)             device.Volume = HScrollBar1.Value             lblvolume.Text = device.Volume & " %"             sWriter.WriteLine("Volume=" & device.Volume.ToString)             sWriter.WriteLine("Mute=" & device.IsMuted.ToString)             If device.IsMuted = True Then                 lblmute.Text = "Muted"                 btnmute.Text = "Unmute"             Else                 lblmute.Text = "Unmuted"                 btnmute.Text = "Mute"             End If             sWriter.Close()         Catch         End Try     End Sub     Private Sub NotifyIcon1_MouseDoubleClick(sender As Object, e As MouseEventArgs) Handles NotifyIcon1.MouseDoubleClick         'set low interval so when the form becomes visible it will also show the changes made trough the windows sound control         Timer1.Interval = 300         Timer1.Enabled = True         Me.Show()         Try             Me.WindowState = FormWindowState.Normal             If device.IsMuted = True Then                 lblmute.Text = "Unmuted"                 btnmute.Text = "Mute"             Else                 lblmute.Text = "Muted"                 btnmute.Text = "Unmute"             End If         Catch         End Try     End Sub     Private Sub Button1_Click_1(sender As Object, e As EventArgs) Handles Button1.Click         'hide the program again         Me.Hide()         Me.ShowInTaskbar = False         Timer1.Interval = 45000     End Sub     Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick         'save settings in the background every 45 seconds         lblvolume.Text = device.Volume & " %"         HScrollBar1.Value = device.Volume         If device.IsMuted = True Then             lblmute.Text = "Muted"             btnmute.Text = "Unmute"         Else             lblmute.Text = "Unmuted"             btnmute.Text = "Mute"         End If         Dim sWriter As System.IO.StreamWriter = Nothing         sWriter = New IO.StreamWriter(strappdata.ToString & "\SetVolume\Volume.ini", False)         sWriter.WriteLine("Volume=" & device.Volume.ToString)         sWriter.WriteLine("Mute=" & device.IsMuted.ToString)         sWriter.Close()         'make me jump in font of all windows when i am visible (!update this might make you loose focus on other apps comment it out to prevent this by adding ' in front)         If Me.Visible = True Then Me.Activate()     End Sub     Private Sub AboutToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles AboutToolStripMenuItem.Click         'handle about menu item         Form2.ShowDialog()     End Sub     Private Sub ExitProgramToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles ExitProgramToolStripMenuItem.Click         'handle exit menu item         Me.Close()     End Sub     Private Sub Form1_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing         'save settings befor closing the app         Try             Dim sWriter As System.IO.StreamWriter = Nothing             sWriter = New IO.StreamWriter(strappdata.ToString & "\SetVolume\Volume.ini", False)             device.Volume = HScrollBar1.Value             lblvolume.Text = device.Volume & " %"             sWriter.WriteLine("Volume=" & device.Volume.ToString)             sWriter.WriteLine("Mute=" & device.IsMuted.ToString)             If device.IsMuted = True Then                 lblmute.Text = "Muted"                 btnmute.Text = "Unmute"             Else                 lblmute.Text = "Unmuted"                 btnmute.Text = "Mute"             End If             sWriter.Close()         Catch         End Try     End Sub     Private Sub OpenAppDataToolStripMenuItem1_Click(sender As Object, e As EventArgs) Handles OpenAppDataToolStripMenuItem1.Click         'handle open appdata menu item         Process.Start("c:\windows\explorer.exe", strappdata.ToString)     End Sub     Private Sub AboutToolStripMenuItem1_Click(sender As Object, e As EventArgs) Handles AboutToolStripMenuItem1.Click         Form2.StartPosition = FormStartPosition.CenterScreen         Form2.ShowDialog()         Me.Activate()     End Sub     Private Sub ShowFormToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles ShowFormToolStripMenuItem.Click         Timer1.Interval = 300         Timer1.Enabled = True         Me.Show()         ' Me.ShowInTaskbar = True         Me.WindowState = FormWindowState.Normal         If device.IsMuted = True Then             lblmute.Text = "Unmuted"             btnmute.Text = "Mute"         Else             lblmute.Text = "Muted"             btnmute.Text = "Unmute"         End If     End Sub End Class This is the main program Add yor own text to the label on form2 currently it is : Program to store the default audio device volume settings within a VDI session. The program will restore the sound level of the default sound device on a session reconnect. Settings are stored in %AppData%\SetVolume\Volume.ini © Mark Platte 2020 mimenl@gmail.com I would appreciate it if you would leave this in. Add the following code to Form2 :smileyinfo: This is the about stuff but since code from Form1 needs it I added it here too From2 Public Class Form2     Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click         Me.Hide()     End Sub     Private Sub lbldonate_LinkClicked(sender As Object, e As LinkLabelLinkClickedEventArgs) Handles lbldonate.LinkClicked         System.Diagnostics.Process.Start(e.Link.LinkData.ToString())     End Sub     Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load         lbldonate.Links.Clear()         Try             'jkust a check to take out the label when running i a known domain environment             If Environment.UserDomainName.ToUpper.ToString <> "somedomain" Then                 If Environment.UserDomainName.ToUpper.ToString <> "somedomain1" Then                     lbldonate.Visible = True                     Label2.Visible = True                     lbldonate.Text = "Donate"                     lbldonate.Links.Add(0, 6, "https://paypal.me/mimenl")                 End If             End If         Catch a As Exception             lbldonate.Visible = True             Label2.Visible = True             lbldonate.Text = "Donate"             lbldonate.Links.Add(0, 6, "https://paypal.me/mimenl")         End Try     End Sub End Class Taking this out will have some impact on changing the code and the components when you build it from scratch based on the provided code samples. If you download the source it can be changed quite easy. But I would like / appreciate it if you would keep mentioning me in some way. I just decide  to share since this problem was really bugging me and can be quite an inconvenience for people around you. Well I hope it will also work for you, running in debug mode from Visual studio might not always work. Compiling and running the compiled exe seems to work better. There is not much error handling and I could only test it in our environment so let me know if things don’t work out so I can see if I can fix it. The user settings are stored in the roaming appdata folder and can easily be saved using DEM. Basically the flow of the application is like this  : Save usersettings -> user disconnects-> user reconnects -> unlock/reconnect event is triggers-> Load usersettings-> restore settings -> volume is on desired level. As mentioned before the reconnect / unlock event is not always trigger before actual sound is played, which might still cause the sound to be at 100 % for a short while. Conclusion Well it seems that something as simple as sound is not so simple after all. I still think that it’s fundamentally wrong that we see the 100 % behavior happening, and still feel it should be fixed in a different way. Maybe by letting the horizon agent interact with the base system and make it able to change sound setting there. Also it seems that windows 10 itself has some weird behavior too. Try muting your VDI without the program running, while playing something on youtube, now reconnect sound will be at 100% you hear YouTube w10 will still show your device is muted. The fixes in this post seem to work in our environment but might still not be the best but for now we don’t have much choice. The fun part is that even when a lot of people stated it couldn’t be done there is still something that does the trick ! Let me know what you think, and if you need any help or want to get a discussion going on sound and horizon I would be happy to join. In the end I hope that all these things are not necessary and that we are doing something totally wrong and that someone could tell me how to fix it without any self-build tools just by using what’s available in DEM and Horizon. But is sure was a great learning experience. Well time to log off again, and in case the code doesn't appeal to you : "It isn't code it's spaghetti "
Ok started something at : The “not so“ nice things about sound and Horizon in a healthcare environment.
So here we go Part 1 The post will be setup in the following way. Intro into the environment. Background story . Problem descriptions. Fixes implemented. Conclusion. Intro into the... See more...
So here we go Part 1 The post will be setup in the following way. Intro into the environment. Background story . Problem descriptions. Fixes implemented. Conclusion. Intro into the environment Since last year we are running a large VDI project within a hospital and we chose VMware Horizon as our main VDI solution, next to Horizon we use DEM for personalization and saving user settings and currently run Window 10 VDI’s. we use Instant clones to provide VDI’s to end users and try to use application virtualization as much as possible. Aslo Tap And go was one of the key points to switch to VDI During the project we had some nice let’s call it challenges and one of the challenges mainly focused around sound, and I mean SOUND in every aspect. Background story This is just a  description (sum up) about the things we encountered here everything looks like it came in at once but in reality things emerged during the project. As always fixing something on the left might always break something at the right. So there were where after some hard work finally able to roll out VDI’s to the business everything seemed cool, the tests were successful and we had full confidence we build something nice. But after a short while the first issues started popping up (this article will only focus on sound issues though). One of the first issues that we heard of was the fact that sound was inconsistent. Some employees had loud sounds other employees could not get the sound loud enough and the last group didn’t get sound all. The irony here is actually quite fun once you fix the issues. So once we fixed this we ran into something else the sound was always to loud, well just reset it or change it after log on this worked but settings didn’t get saved (many things about this already on the internet). So we went for the log on log of task approach, this seemed to work fine for a while, at least for the people that worked all day at the same desktop from which they never disconnected to their VDI. This was basically the type of user that we started rolling out VDI’s too at start. But then we started rolling out VDI’s to nurses , and if you can say one thing here is that they don’t stay at one spot for a long time. They switch clients a lot since the VDI moves with them. The nursing PC’s are all turned to Kiosk PC’s and there the new issue came up. On every reconnect to the VDI session the sound level was increased to 100%. This might not really look like a big issue and during the day it didn’t matter that much but imagine the following scenario. You are sleeping as a patient the nurse comes to check on you. But since it’s the night shift in which they also spend a lot of time waiting the nurse decided to watch a nice action movie on Netflix. She turned her sound to 20 % so no one can hear it before she started doing her checkups. She swipes out (tap and go / disconnect her session oh and leaves Netflix running just before that action scene were a bomb explodes) She than takes a mobile card moves it to the patients room, swipes her card connects to her session, sounds are raised to 100 % Netflix still playing bomb explodes and all patients are awake. Not really the wakeup call you would like. So this needs to be fixed (I hear you think but NO blocking Netflix is not an option). And then  for the final part of the story corona happened, nothing too big since the VDI’s can be used from home too so this was quite easy except for the part were video conferencing became hot. So there come the microphones and webcams, and the issues of the microphone not being loud enough and still some devices not playing sound on the wanted levels. So see here the challenge we had to face, and even the answers on some things that they could not be fixed (which for now doesn’t seem true) but depends on policies since the solution will involve some programming and making use of third party tools some of them were already mentioned in prior posts but I will name them again. Below I will try to list the different issues in a more point like manner. Problem descriptions pointed : High/low sound volume, no sound at all. Microphone not boosted. 100% sound level reset at reconnect. Fixes Implemented: General notice : a lot of my solutions are programmed in VB.net I just like it and it saves me time compared to finding my way around in PowerShell. I do think the solutions can also be achieved in PowerShell but this is something you need to do yourself, at least the VB code might give you some general ideas about how to script it. I will add the source code to the projects were possible so it can be changed and compiled, the community edition of VB.net should be sufficient. I can also provide the compiled binaries but I rather not due to the fact they are .exe files and that always is tricky. (and I didn’t fully read the rules so it might not even be allowed)   High/low sound volume, no sound at all. Trouble shooting this issue was actually quite easy, apparently horizon takes over settings  from the base system when it comes to sound, making the VDI sound relative to current level of the sound volume settings of the client PC Ex. You put your base system om 50 % in this case sound in the VDI at 100 % is still 50 % of the systems possible volume. Same goes for muting muting your base system will not give you sound trough VDI. And since we run kiosk pc’s users cannot change this themselves. So this needed to be changed by IT. Our approach. Since we needed to change a lot of systems it needed to be done in a scripted way. We run W10 as base OS and sound is not easily managed programmatically due to the fact settings are stored in driver classes and not in registry luckily there is a nice tool from NirSoft called SoundVolumeView.( https://www.nirsoft.net/utils/sound_volume_view.html) this tool allows you to set the volume of different devices. Even though the tool can do a lot for you it would still ask a lot of manual work at least in our environment. We are using a lot of different hardware and drivers so the sound device might be called different on different machines, it is not always clear were sound devices are connected (back port / front port) so lots of options to tackle. The tool however also has a nice option /scomma <Filename> which lets you export all the devices on a machine to a csv file, this file can then be used to set all the devices to a certain level 100 % in our case. The tool however does not have an option to do this by itself so here we need to create a custom application/ script. Module Module1     Sub Main()         'declare variables and prepare some things for later use.         Dim strsourcepath As String = "\\test-path\\"         Dim strAppname As String = "SoundVolumeView.exe"         Dim strIstFolder As String = ""         Dim strSolFolder As String = ""         Dim strCompname As String = My.Computer.Name         Dim commandlimeargs As String() = Nothing         Dim strfilename As String = ""         Dim srFilereader As System.IO.StreamReader = Nothing         Dim p As New Process()         Dim pStartInfo As New ProcessStartInfo()         Dim pset As New Process()         Dim psetStartInfo As New ProcessStartInfo()         Dim strLine As String = ""         Dim strsplit As String() = Nothing         Dim strDeviceName As String = ""         Dim bDebug As Boolean = False         Try             'create a filename to use for saving the csv file (computername_date_without spaces and :)             strfilename = strCompname & "_Audio_" & Date.Now             strfilename = Replace(strfilename, " ", "_", 1,, CompareMethod.Text)             strfilename = Replace(strfilename, ":", "-", 1,, CompareMethod.Text)             commandlimeargs = Environment.GetCommandLineArgs()             'check for arguments and take proper action             For i = 0 To commandlimeargs.Length - 1                 If commandlimeargs(i).ToLower.ToString = "-path" Then                     strsourcepath = commandlimeargs(i + 1)                 End If                 If commandlimeargs(i).ToLower.ToString = "-debug" Then                     bDebug = True                 End If             Next i             'set folder location for file storage             strIstFolder = strsourcepath & "\" & "Ist\"             strSolFolder = strsourcepath & "\" & "Sol\"             'peroare process to launch SoundVolumeView.exe             pStartInfo.FileName = strsourcepath & "\" & strAppname             If bDebug = True Then                 Console.WriteLine("Creating file : " & strIstFolder & strfilename & ".csv")             End If             'set parameters for SoundVolumeView.exe to create csv file             pStartInfo.Arguments = "/scomma " & strIstFolder & strfilename & ".csv"             'hide SoundVolumeView.exe window             p.StartInfo.CreateNoWindow = True             p.StartInfo = pStartInfo             ' start process             p.Start()             p.WaitForExit(60000) ' wait for the process to finish and continue after 60 seconds if process isn't finished             'prepare cvs file for reading             srFilereader = New System.IO.StreamReader(strIstFolder & strfilename & ".csv")             'read cvs file to prefprm actions             While srFilereader.Peek > -1                 strLine = srFilereader.ReadLine                 strsplit = Split(strLine, ",", , CompareMethod.Text)                 'check for device entries                 If strsplit(1).ToLower.ToString = "device" Then                     If bDebug = True Then                         Console.WriteLine("Found device : " & strsplit(0).ToString)                     End If                     'prepare process to set volume based on csvfile entries                     psetStartInfo.FileName = strsourcepath & "\" & strAppname                     If bDebug = True Then                         Console.WriteLine("Settig volume to 100 % for device : " & strsplit(0).ToString)                     End If                     'set volume of device                     psetStartInfo.Arguments = "/setvolume " & Chr(34) & strsplit(0).ToString & Chr(34) & " " & "100"                     pset.StartInfo = psetStartInfo                     pset.Start()                     pset.WaitForExit(60000)                     If bDebug = True Then                         Console.WriteLine("Unmuting device : " & strsplit(0).ToString)                     End If                     'unmute device                     psetStartInfo.FileName = strsourcepath & "\" & strAppname                     psetStartInfo.Arguments = "/Unmute " & Chr(34) & strsplit(0).ToString & Chr(34)                     pset.StartInfo = psetStartInfo                     pset.Start()                     pset.WaitForExit(60000)                     If bDebug = True Then                         Console.WriteLine("Finished configuring device : " & strsplit(0).ToString)                     End If                 End If             End While             If bDebug = True Then                 Console.WriteLine("Creating file : " & strSolFolder & strfilename & ".csv")             End If             'create csv file containing results             pStartInfo.Arguments = "/scomma " & strSolFolder & strfilename & ".csv"             p.StartInfo.CreateNoWindow = True             p.StartInfo = pStartInfo             p.Start()             p.WaitForExit(60000)             Threading.Thread.Sleep(10000)             srFilereader.Close()         Catch ex As Exception             Console.WriteLine("Something went wrong see error description below :")             Console.WriteLine(ex.ToString)             Threading.Thread.Sleep(10000)         End Try     End Sub End Module To create this program start a new console application in vb.net call it SetBaseVolumes and copy the above code to module1.vb The application has a couple of command line arguments and needs some setup. -path <path>  the specified path will be used as path to look for the SoundVolumeView.exe file. -debug will show information in the command prompt else it will just stay black till the process is finished. Setup: In the folder that is specified with the path parameter create 2 subfolders : Sol and Ist. Your folder setup would look like this : Path Path\sol Path\ist The folders are used to store the csv file prior to setting values and after so you can check if things changed from a central location. The application will be compiled to SetBaseVolumes.exe It is advised to store the executable on a network share and also create a network folder to store the SoundVolumeView.exe also create the ist and sol folder in this directory. Make sure the program runs under a user with network access we use Altiris with a service account to achieve this, and push a job to all machines. The syntax could be something like this \\appfolder\setbasevolumes.exe -path \\destfolder\csv Don’t add the final \ to the destination path this is added by the program. Working : The first part of the code is used to declare some variables and prepare some things to launch the processes in a proper way. Step 1 create a filename based on computer name and time stamp Step 2 create csv file with all devices for the machine it is run on Step 3 read csv and set volumes Step 4 create file with results. I added some minor comments to the code but feel free to ask questions if needed. The end result will be that all connect devices on the base (client kiosk pc) will have the volume level set to 100 % and if the device was muted it will be unmuted. This will only work for connected devices so make sure all the things needed by the end user are plugged in. So that was part 1 of the solution covering the sound levels of the base machine which seem to be used by horizon as relative sound levels. Microphone not boosted. So after we fixed the sound issues of the base system the microphone issue popped up. This also seems related to the base system since VMWare uses the microphone array in the VDI which basically seems to use the local machine settings for the microphone. We would also like to set this scripted but this device is not listed in the CSV. Apparently the “Microphone Boost” or “Microfoonversterking” is a virtual device which can be set by SoundVolumeView.exe but with a slightly different command line option. In this case we used : soundvolumeview.exe /setvolumedecibel “[device name]” 24 This will set it to the maximum boost. Keep in mind that depending of the OS language this device name will change in this case Microfoonversterking = Dutch Microphone Boost = English This command should also be run from the pc that needs the setting and the microphone should be plugged in. Once these setting are done you can manage your sound levels within the VDI by using the master volume and in applications the microphone array settings. This kind off covered the first two issues from the issue list (for us) so up to the next one. The sound reset on reconnect. (part 2)
Ok as the tile suggest I would like to elaborate a bit about sound issues within a horizon environment within a healthcare setting. But first things first, this might be a different type of po... See more...
Ok as the tile suggest I would like to elaborate a bit about sound issues within a horizon environment within a healthcare setting. But first things first, this might be a different type of post than the usual ones, I might make some strange and wrong assumptions, be bold  at some times but i still think the story might be interesting and might even help solving some issues for others. Since English is not my native language please bear with me. Also feel free to correct wrong assumptions and let's try together to make this post as helpful as it can possibly be. And since this is my first post let me introduce myself a bit : Mark Platte 36 years old and currently working as an IT architect in a hospital environment. But my roots are in application development. I like to solve issues and still believe that when it comes to IT everything can be fixed depending on how much effort and endurance (and too bad also money) you are willing to spend on solving issues. Although determination might actually be the real key word here. So enough about me so here we go : It might be i'm not able to finish all of the post so it may come in a couple of posts.
Great, Il'lstart something new and will put a link here in case people still find this topic.
Is this issue still active ? I might have some ideas here  or is it better to open up something new ?