Powershell: Send a message to all user within a Citrix Delivery Group

Usage:
send-ctxmessage -DesktopGroupName DG1 -Time "20 Minutes"

You can add different parameters and put them into your message text!
So by adding a $Application parameter you will be able to do the following:
sent-ctxmessage -desktopgroupname DG1 -Time "20 Minutes" -Application "AppXY"
Modify your message:
Send-BrokerSessionMessage -AdminAddress $AdminAddress -InputObject $session -MessageStyle Information -Title "Update of $Application" -Text "In $Time there will be an Update for $Application."

What the User will receive:
Update of AppXY
In 20 Minutes there will be an Update for AppXY

function send-CTXmessage{
    param( 
            [string]$DesktopGroupName,
            [string]$Time,
            $adminAddress = "Insert Name of Controller here!"
         )
    $ErrorActionPreference = "Stop"
    try
    {
 
        Add-PSSnapin Citrix* -ErrorAction Stop
        $arrSrv = get-brokerMachine -adminAddress "$AdminAddress" | where-object {$_.DesktopGroupName -eq "$DesktopGroupName"} | select-object MachineName
        
        
        
            
         foreach ($srv in $arrSrv) {
                
                 $arrSessions = @()
                 $arrSessions = Get-BrokerSession -AdminAddress $AdminAddress -MachineName $srv.machineName | Sort-Object

                 foreach ($session in $arrSessions){
                     
                     Send-BrokerSessionMessage -AdminAddress $AdminAddress -InputObject $session -MessageStyle Information -Title "Insert Message Title here" -Text "In $Time there will be some Update, please log off"                        
                     write-host "User "$session.UserFullName" Message send" -foregroundColor Green
                 }
         }
        
    }
    catch
    {
        write-host "Caught an exception:" -ForegroundColor Red
        write-host "Exception Type: $($_.Exception.GetType().FullName)" -ForegroundColor Red
        write-host "Exception Message: $($_.Exception.Message)" -ForegroundColor Red
    }
    finally
    {
        write-host "--End Script--"
    }
 }


Powershell: Get Weather Function (Working with XML and API’s)

Some more fun with Invoke-Webrequest’s and API’s.
OpenWeatherMap (http://openweathermap.org) offers a free service for their API key.
They offer many different ways on how to access their weather.

Like last time, I chose XML.
The API Request looks like this:

Invoke-WebRequest "api.openweathermap.org/data/2.5/weather?q=$City,$country&APPID=$api&mode=xml"

you add a q= Query with the City Name and for accuracy the Country. Then you give it an APPID. This is your own personal API Key that I won’t publish. You can get yours for free on the openweathermap site. Then you specify the mode, the way the request returns it’s values.

If I run the request you get the following in return:
xmlapiweather1

these are really mostly stuff we won’t need. What I am interested in is the content.
To get the content of the XML, we need to tell Powershell that the return values are in fact in XML format. Like this:

$City = "Lucerne"
$country = "CH"
[ xml ]$wr = Invoke-WebRequest "api.openweathermap.org/data/2.5/weather?q=$City,$country&APPID=$api&mode=xml"

Remove the spaces in xml in front of $wr – my script displaying engine does not like the term like that 🙂

Now, to be able to search through the XML, we need to see which member contains all the Information.
That’s easy to find out since the only member with the Type „Property“ is the one we are looking for. (Or the ones, if you query several days / cities)
xmlapiweather2

Well – if we put in $wr.current we’ll see that this is exactly what we want.
city : city
temperature : temperature
humidity : humidity
pressure : pressure
wind : wind
clouds : clouds
visibility :
precipitation : precipitation
weather : weather
lastupdate : lastupdate

These are all the properties we can use for our weather.
If you want to see what’s inside of Temperature you can do the following:

$wr.current.temperature

It will return something like this:
xmpapiweather3
to just get one of these you can do this:

$wr.current.temperature.value

(or min, max and unit for that example)

Now we have a query that returns us one single response. That’s great we can work with that.

Since it’s a bit redundant to always have to enter $wr.current we give it another $Variable

 $data = $wr.current 

Now we need to build our response.
I want it like that:
City:
Weather:
Temp. NOW:
Temp. MAX:
Temp. MIN:
Humidity:
Clouds:
Rain:
Wind:
Pressure:

To achieve this I first browse through all the properties and write down the values I want.
Then I simply build a write-host construct.

    write-host "City:          " $data.city.name $data.city.country
    write-host "Weather:       " $data.weather.value
    write-host "Temp. NOW:     " ([math]::Round(($data.temperature.value - 273.15),2))"°C"
    write-host "Temp. MAX:     " ([math]::Round(($data.temperature.max - 273.15),2))"°C"
    write-host "Temp. MIN:     " ([math]::Round(($data.temperature.min - 273.15),2))"°C"
    write-host "Humidity:      " $data.humidity.value $data.humidity.unit
    write-host "Clouds:        " $data.clouds.name
    write-host "Rain:          " $data.precipitation.mode
    write-host "Wind:          " $data.wind.Value
    write-host "Pressure:      " $data.pressure.value

Remember when we were displaying the temperature values? They were in Kelvin. To get Celsius simply subtract 273.15 (like we learned in school a long time ago :))
But the values are really exact. – I’ve rounded them with the C# lib „math“ to two digits after the comma.

Next step is to make a function out of it. This needs parameters. I added default values for my most searched city:

function get-weather {
    param(
        [string]$City = "Lucerne",
        [string]$country = "CH"
    )
...
}

you can get the weather with
get-weather Washington US
If you don’t specify the country it will always be the one you entered as $country in your params.

Now the whole script:

function get-weather {
    param(
        [string]$City = "Lucerne",
        [string]$country = "CH"
    )

    $api = "get your API at openweathermap.org"
    
    [ xml ]$wr = Invoke-WebRequest "api.openweathermap.org/data/2.5/weather?q=$City,$country&APPID=$api&mode=xml"
    $data = $wr.current
    
    
    write-host "City:          " $data.city.name $data.city.country
    write-host "Weather:       " $data.weather.value
    write-host "Temp. NOW:     " ([math]::Round(($data.temperature.value - 273.15),2))"°C"
    write-host "Temp. MAX:     " ([math]::Round(($data.temperature.max - 273.15),2))"°C"
    write-host "Temp. MIN:     " ([math]::Round(($data.temperature.min - 273.15),2))"°C"
    write-host "Humidity:      " $data.humidity.value $data.humidity.unit
    write-host "Clouds:        " $data.clouds.name
    write-host "Rain:          " $data.precipitation.mode
    write-host "Wind:          " $data.wind.Value
    write-host "Pressure:      " $data.pressure.value
}

Remove the spaces in xml in front of $wr – my script displaying engine does not like the term like that 🙂

Quick Spellcheck with PowerShell

I’m a person that always needs to google words just to see if they even exist. I made a quick Powershell for that just to check whether or not the word even exists (by using the Wikipedia API – Which normalizes most inputs by itself)

The wikipedia API query will look like this:

https://$lang.wikipedia.org/w/api.php?action=query&titles=$word&format=xml

It’s really easy – at the beginning of the link you set the language your word will be. I’ll set that with the var $lang.
After the domain you’ll tell it to use the public API (Documentation here. )
I want to do a query (action=query) on all sites that have a title containing my $word variable. (titles=$word) and I want it in XML because it’s quite easy to handle in Powershell (format=xml).

Now I’ll need some input for my variables:

function check-word {

    Param(
      [string]$word,
      [string]$lang
    )
...
}

That way the quick spellchecker can now be used like that: check-word wordpress de. This tells the API to look for German pages containing the word „wordpress“.

Next thing is to get the Data from the API:

$url = "https://$lang.wikipedia.org/w/api.php?action=query&titles=$word&format=xml"
$webq = Invoke-WebRequest $url

You could do this in one line but for troubleshooting and understanding it’s better to keep it separated.

$webq will look like this:
webq-output1

In content you’ll see it normalized the query.

If I mistype the query it will look like this:
webq-output2

As you see here Content contains the word missing. That’s the API’s way of telling you that it doesn’t exist.
Now we can work with that.
Next step is to check for that word missing:

$webqc = $webq | select-object content
$exists = $webqc -match "missing"

No here comes the tricky thing: $exits will either contain $true or $false. But it will contain $true if the word DOES NOT exist. And it will contain $false if the word exists.

It’s so important because we want to see a little output and have to think reversed:

if ($exists) {Write-Output "Not Correct"} else {"Correct"}

So after all this there’s not much left to do, here’s the full script:

function check-word {
    Param(
      [string]$word,
      [string]$lang
    )
    
    $url = "https://$lang.wikipedia.org/w/api.php?action=query&titles=$word&format=xml"
    $webq = Invoke-WebRequest $url
    $webqc = $webq | select-object content
    # Variable will contain $true if word does not exist - $false if word exists
    $exists = $webqc -match "missing"
    
    if ($exists) {Write-Output "Not Correct"} else {"Correct"}
}

Put it in your $profile and you’ll always have quick quick spellcheck handy if you just need to see if you wrote that word correctly.
If you want to use it in a work environment make sure to check out how to authenticate your shell against a proxy here: Automatic Proxy Authentication from $Profile

If you got any questions or tips and tricks on how to make this script better, leave a comment!

POSH: Using ~ for your home directory

Out of habit I used

cd ~

in the Powershell ISE today. Lucky for me Powershell then showed me that you can actually use ~ for your home directory. Just enter the following command:

(get-psprovider 'FileSystem').home = "P:\ath\to\Home"

As soon you have done this you’ll never have to break out of habit to get to your main folder.

Powershell: Get your printers ready

Ready to use functions for your next printer script:

Create Printer

script

function CreatePrinter {
$server = $args[0]
$print = ([WMICLASS]"\\$server\ROOT\cimv2:Win32_Printer").createInstance()
$print.drivername = $args[1]
$print.PortName = $args[2]
$print.Shared = $true
$print.published = $true
$print.Sharename = $args[3]
$print.Location = $args[4]
$print.Comment = $args[5]
$print.DeviceID = $args[6]
$print.Put()
}

usage

createprinter servername drivername portname sharename location comment deviceID

DeviceID and Sharename may be the same. Disable $print.shared and .published if you do not want the printer to be created in AD.
This is great for creating a printer via CSV File.

Create Printer Port

function CreatePrinterPort {
$server =  $args[0]
$port = ([WMICLASS]"\\$server\ROOT\cimv2:Win32_TCPIPPrinterPort").createInstance()
$port.Name= $args[1]
$port.SNMPEnabled=$false
$port.Protocol=1
$port.HostAddress= $args[2]
$port.Put()
}

usage

CreatePrinterPort servername portname hostaddress

Create the ports first if they do not already exist!

Delete Printer

function DeletePrinter {
    param(
        $Server,
        $Printer
    )
    Get-WmiObject -class "Win32_Printer" -ComputerName $Server -namespace "root\CIMV2" |
    Where-Object {$_.name -eq $printer} | ForEach-Object {
        $_.Delete()
    }
}

Rename Printer


function RenamePrinter {
    param(
        $server,
        $oldName,
        $NewName
    )
    Invoke-WmiMethod -path "Win32_Printer.DeviceID='$oldname'" -name RenamePrinter -ArgumentList "$newname" -ComputerName "$server"
}

MOTD for Powershell (Using the fortune-mod (linux) Package files)

I downloaded and altered the fortune-mod fortune files so that there’s one fortune per line. This can be downloaded here:
http://pastebin.com/TnxeyWJm

Next step is to add the following thing to your $Profile

function get-motd{
$fortunes = get-content \path\to\fortunes.txt
get-random -InputObject $fortunes
}
.......
clear-host
get-motd{}

It’s virtually important that you run the function after the clear-host. Otherwise the quote will not appear / will be erradicated before it even had time to present itself to you)

Powershell: Select CSV Colums to create new CSV

My first small project with Forms. Run the script with .\modifycsv.ps1 -file .\csvfile.csv

[CmdletBinding()]
Param(
  [Parameter(Mandatory=$True,Position=1)]
    [string]$file,
  [parameter()]
    [string]$delimiter = ";"
)
 
$csv = import-csv $file -Delimiter $delimiter
$headers = $csv | get-member -membertype NoteProperty | select-object -ExpandProperty 'Name'
$x = @()
 
 
$headers = $csv | get-member -membertype NoteProperty | select-object -ExpandProperty 'Name'
$x = @()



<# FORM START ---------------------------------------------------------------------#>
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") 

$objForm = New-Object System.Windows.Forms.Form 
$objForm.Text = "Data Entry Form"
$objForm.Size = New-Object System.Drawing.Size(300,200) 
$objForm.StartPosition = "CenterScreen"

$objForm.KeyPreview = $True

$objForm.Add_KeyDown({if ($_.KeyCode -eq "Enter") 
    {
        foreach ($objItem in $objListbox.SelectedItems)
            {$x += $objItem}
        $objForm.Close()
    }
    })

$objForm.Add_KeyDown({if ($_.KeyCode -eq "Escape") 
    {$objForm.Close()}})

$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(75,120)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"

$OKButton.Add_Click(
   {
        foreach ($objItem in $objListbox.SelectedItems)
            {$x += $objItem}
        $objForm.Close()
   })

$objForm.Controls.Add($OKButton)

$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Size(150,120)
$CancelButton.Size = New-Object System.Drawing.Size(75,23)
$CancelButton.Text = "Cancel"
$CancelButton.Add_Click({$objForm.Close()})
$objForm.Controls.Add($CancelButton)

$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,20) 
$objLabel.Size = New-Object System.Drawing.Size(280,20) 
$objLabel.Text = "Please make a selection from the list below:"
$objForm.Controls.Add($objLabel) 

$objListbox = New-Object System.Windows.Forms.Listbox 
$objListbox.Location = New-Object System.Drawing.Size(10,40) 
$objListbox.Size = New-Object System.Drawing.Size(260,20) 

$objListbox.SelectionMode = "MultiExtended"

<# DATA INPUT -------------------------------------------------------------#>
write-output $headers | ForEach-Object {[void] $objListBox.Items.Add($_)}


$objListbox.Height = 70
$objForm.Controls.Add($objListbox) 
$objForm.Topmost = $True

$objForm.Add_Shown({$objForm.Activate()})
[void] $objForm.ShowDialog()
<# FORM END --------------------------------------------------------------#>



Write-Output $csv | select-object -property $objlistbox.selecteditems | export-csv .\CSVOutput.csv -NoTypeInformation

Citrix: RestartBrokerAgent for your Service Desk

If users cannot be logged off through the Citrix Director it mostly can be solved by restarting the Broker Agent (Citrix Desktop Service) Service.
It’s a short procedure but if done fast and dirty can cause many errors (by OSI Layer 8). I wrote a really small script so everyone can easily restart the service.

<code>param(
[string]$server
)
get-service -ComputerName $server -name BrokerAgent | restart-service -Verbose</code>

Just give it to the Service Desk and they can now restart the BrokerAgent with .\restartbrokeragent.ps1 -server XA-Server

Powershell: Get Clipboard Content to use with your script

I wrote a little script for a Reddit user on /r/techsupport that allows him to download a youtube Video with copying the URL and just starting the script. (Download with youtube-dl https://github.com/rg3/youtube-dl/)

The Clipboard part was the most fun and I guess some people can use it.

I basically create a WinForm which I won’t show and use the System.Windows.Forms.Textbox „paste()“ function to get the text:

<code>add-type -assemblyname system.windows.forms
$form = new-object System.Windows.Forms.TextBox
$form.multiline = $true
$form.paste()
$form.Text</code>

 

Your Clipboard content will be in $form.Text