Updating AzureAD User Attributes

Hi Everyone!

I recently had the opportunity to use PowerShell to update AzureAD user attributes. This is different from what I normally do as we still leverage an on-prem AD setup.

I’d never used the command before but I know PowerShell and I’m fairly confident with the AD PowerShell commands.

The mission at hand was this: Update AzureAD user attributes so that the Marketing department had new address information

The first and rather dirty method I put together as a proof-of-concept is below:

$marketingUsers = Get-AzureADUser Filter "Department eq 'Marketing'"
foreach($user in $marketingUsers){
Set-AzureADUser ObjectID $user `
StreetAddress '51 River St.' `
City 'Ridgefield' `
State 'CT' `
PostalCode '06877' `
Country 'United States'
}

I know right, it’s ugly. It’s lacking any form of error checking, there’s no host output and it’s hard to read.

What I did next was put my code behind a few checks. You can see the improved code below:

#Get all marketing users
$marketingUsers = $null
try {
$marketingUsers = Get-AzureADUser Filter "Department eq 'Marketing'" ErrorAction Stop
}catch{
#Output the error message if any
Write-Host "Failed to collect Marketing users!" ForegroundColor Red
Write-Host $_.ScriptStackTrace ForegroundColor Red
}
#Checking if there are no marketing users found
if (!$marketingUsers){
Write-Host "No Marketing users found"
return;
}
#Run through each user and update
foreach($user in $marketingUsers){
try{
Set-AzureADUser ObjectID $user `
StreetAddress '51 River St.' `
City 'Ridgefield' `
State 'CT' `
PostalCode '06877' `
Country 'United States' `
ErrorAction Stop
}catch{
Write-Host "Failed to update $user" ForegroundColor Red
Write-Host $_.ScriptStackTrace
}
}

This was looking much better, it handles error nicely but there is still room for improvement…

I want to implement splatting and also look into ways to speed the script up!

I wanted to take a look into speed first. I know there are subtle different between using the -filter parameter and piping the results into a Where-Object commandlet. Lets run some tests!

I ran the below commands 5 times to get an average using Measure-Command and outputted in total miliseconds:

CommandGet-AzureADUser -ErrorAction Stop | Where-Object {$_.Department -eq ‘Development’}Get-AzureADUser -Filter “Department eq ‘Development'” -ErrorAction Stop
#11311.66696630.8861
#21769.62537973.5126
#32122.87496060.9699
#41963.65125315.6691
#53437.2685783.7616

Crazy! Switching from the -filter parameter to using the pipeline more than halved the time it took for the command to run!

Next was to build the hashtable for splatting in the Set-AzureADUser parameters before building the final version. This was simple done by using the below code:

#51 River St., Ridgefield, CT 06877
#Randomly generated fake address
#New props in a hashtable for splatting
$newProps = @{
StreetAddress = '51 River St.'
City = 'Ridgefield'
State = 'CT'
PostalCode = '06877'
Country = 'United States'
}

This now means I can simplify the Set-AzureADUser command.

You can find the full and finished script below:

#51 River St., Ridgefield, CT 06877
#New props in a hashtable for splatting
$newProps = @{
StreetAddress = '51 River St.'
City = 'Ridgefield'
State = 'CT'
PostalCode = '06877'
Country = 'United States'
}
#Get all marketing users
$marketingUsers = $null
try {
$marketingUsers = Get-AzureADUser ErrorAction Stop | `
Where-Object {$_.Department -eq 'Marketing'}
}catch{
#Output the error message if any
Write-Host "Failed to collect Marketing users!" ForegroundColor Red
Write-Host $_.ScriptStackTrace ForegroundColor Red
}
#Checking if there are no marketing users found
if (!$marketingUsers){
Write-Host "No Marketing users found"
return;
}
#Running through each user
foreach ($user in $marketingUsers){
try{
Set-AzureADUser ObjectId $user $newProps ErrorAction Stop
}catch{
Write-Host "Failed to update $user" ForegroundColor Red
Write-Host $_.ScriptStackTrace
}
}

Enjoy!

Creating a Battery Indicator Watch Face for Garmin

Today, I want to show you how to create a very simple battery indicator in Garmin. I want to create a label that shows the current battery percentage which also changes colour depending on the percentage level.

So, I’ll start by creating a new label in the watch that I will use. This is my layout.xml:

<layout id="WatchFace">
<label id="BatteryLabel" x="center" y="center" font="Graphics.FONT_NUMBER_THAI_HOT" justification="Graphics.TEXT_JUSTIFY_CENTER" color="Graphics.COLOR_WHITE" />
</layout>

Now, in the View.mc file I will use this label to add the battery percentage and assign a colour based on that percentage.

First, I will paste in my updateDisplayObject method to update the label text:

//Update the display object text
function updateDisplayObject(updateObject, updateText){
var _viewObject = View.findDrawableById(updateObject) as Text;
_viewObject.setText(updateText);
}

I will then create a method to update the battery label’s text colour based on the battery percentage. This will need to take in the battery percentage as a parameter:

//Update the battery text color
function updateBatteryTextColor(updateBatPerc){
var _color = Graphics.COLOR_GREEN;
//Setting battery label color based on percentage
if (updateBatPerc > 75){
_color = Graphics.COLOR_DK_GREEN;
}
if (updateBatPerc < 75 && updateBatPerc >= 25){
_color = Graphics.COLOR_YELLOW;
}
if (updateBatPerc < 25){
_color = Graphics.COLOR_RED;
}
var _viewObject = View.findDrawableById("BatteryLabel");
_viewObject.setColor(_color);
}

From the above code, you can see I am assigning a specific color based on the batter percentage on a three tier system. The label colour will be based on the following rules:

ValuesColor
Greater than 75%Green
Lower than 75% and higher than or equal to 25%Yellow
Lower than 25%Red
Battery label colour table

I can now update the onUpdate method to use the above methods to show the battery indicator correctly. This is what the onUpdate method looks like:

// Update the view
function onUpdate(dc as Dc) as Void {
//Get and show the current battery percentage
var pwr = System.getSystemStats().battery;
var batStr = Lang.format( "$1$%", [ pwr.format( "%2d" ) ] );
updateDisplayObject("BatteryLabel", batStr);
//Update battery text color
updateBatteryTextColor(pwr);
// Call the parent onUpdate function to redraw the layout
View.onUpdate(dc);
}

After all that, you can see the watch face showing the correct three colours below:

Red battery indicator
Yellow battery indicator
Green battery indicator

Enjoy! ?

Creating a Simple Garmin Watch Face

So, I recently got into running and picked up a bargain Garmin watch. I looked on the marketplace for a simple watch face that had the attributes I wanted. I found nothing that suited my needs.

The apps from the app store were often too complex looking or lacked functionality.

This is where I started researching how to create a custom watch face and maybe even post it to the marketplace if possible. I learnt that Garmin uses Monkey C along with their SDK to create everything from watch faces to apps.

First things first, you need to setup the environment and download the SDK: https://developer.garmin.com/connect-iq/sdk/

Once setup, lets open Visual Studio Code and open the command palette using Ctrl+Alt+P. In this prompt, we want to search for the Monkey C: New Project option. It will then ask for the name of the project and the type. For this example, I will select a Simple Watch Face and set the API level to the lowest value.

This will build the project structure and create a base watch face to work from.

We now need to add supported products to the project. My watch is the Garmin Forerunner 735XT but you can use whichever watch you have. Use the command palette again and use the Monkey C: Edit Products option. In that dropdown, select all the watches you want to support and build for.

Now we can hit F5 and run the watch face in the virtual watch:

The prebuilt project from Garmin

In this post, I’m going to build a watch face similar to the below design:

Simple watch face design concept

First, lets edit the layout.xml to build the design of the watch face. This file is in Resources -> Layouts.

Remove the default label from the XML file and lets build the 4 new labels required for our battery percentage, steps, current time and todays date.

The layouts are fairly easy to follow, each label requires the following attributes:

AttributeExplanationPossible Values
idThe name of the label. This will be used in the code to update the value.Any string value starting with a capital letter. E.g. TestLabel
xThe horizontal positioning of the label. This can be a pixel value or the predefined values.left, center, right, pixel value, or a percentage
yThe vertical positioning of the label. This can be a pixel value or the predefined values.top, center, bottom, pixel value, or a percentage
fontThe font to use on the label. These are predefined.All options are documented in the API docs. We are only using values from API level 1.0.0 for now:

https://developer.garmin.com/connect-iq/api-docs/Toybox/Graphics.html
justificationHow the text should position itself in the space provided.TEXT_JUSTIFY_RIGHT,
TEXT_JUSTIFY_CENTER,
TEXT_JUSTIFY_LEFT,
TEXT_JUSTIFY_VCENTER
colorThe colour of the textAll options are documented in the API docs:

https://developer.garmin.com/connect-iq/api-docs/Toybox/Graphics.html

So my new layout.xml file will look like this:

<layout id="WatchFace">
<label id="TimeLabel" x="center" y="center" font="Graphics.FONT_NUMBER_THAI_HOT" justification="Graphics.TEXT_JUSTIFY_CENTER" color="Graphics.COLOR_WHITE" />
<label id="BatteryPercentageLabel" x="center" y="top" font="Graphics.FONT_MEDIUM" justification="Graphics.TEXT_JUSTIFY_CENTER" color="Graphics.COLOR_LT_GRAY"/>
<label id="StepsLabel" x="center" y="10%" font="Graphics.FONT_MEDIUM" justification="Graphics.TEXT_JUSTIFY_CENTER" color="Graphics.COLOR_LT_GRAY"/>
<label id="DateLabel" x="center" y="bottom" font="Graphics.FONT_MEDIUM" justification="Graphics.TEXT_JUSTIFY_CENTER" color="Graphics.COLOR_LT_GRAY"/>
</layout>

Now that the layout is built, we need to build the functionality in the backend to update the view. Open the watch face _View.mc file. This can be found in Source -> watchface_View.mc.

First, lets add a required using statement at the top. Add the following line:

using Toybox.Time.Gregorian as Calendar;

I will also create a new method for updating the view. This method will take in the string ID of the label as well as the new value:

//Update the display object text
function updateDisplayObject(updateObject, updateText){
var _viewObject = View.findDrawableById(updateObject) as Text;
_viewObject.setText(updateText);
}

We can now use the above method to update the view.

We will now head over to the onUpdate method to update the view.

Now, lets work on building the required data and formatting it before updating the view. You can see below how I work through each of the four required sections and use the above updateDisplayObject method to update the view:

// Get and show the current time
var clockTime = System.getClockTime();
var timeString = Lang.format("$1$:$2$", [clockTime.hour, clockTime.min.format("%02d")]);
updateDisplayObject("TimeLabel", timeString);
//Get and show the current battery percentage
var pwr = System.getSystemStats().battery;
var batStr = Lang.format( "$1$%", [ pwr.format( "%2d" ) ] );
updateDisplayObject("BatteryPercentageLabel", batStr);
//Get and show the steps data
var stepCountString = ActivityMonitor.getInfo().steps.toString();
updateDisplayObject("StepsLabel", stepCountString);
//Get and show the date
var now = Time.now();
var info = Calendar.info(now, Time.FORMAT_LONG);
var dateString = Lang.format("$1$ $2$ $3$", [info.day_of_week, info.day, info.month]);
updateDisplayObject("DateLabel", dateString);

Now, when we hit F5 to run the watch face, you can see how the display has changed and we have built the watch layout:

The built simple watch layout

Hope this helps someone out as the documentation is a little lacking. Enjoy! ? ?

Additional Notes – You can also confirm the languages your watch face will support by using the Monkey C: Edit Languages option.

Adding Aliases to AD with PowerShell

Adding aliases in AD is a VERY simple task.

You can just find the user via the ADUC (Active Directory Users and Computers), go to the ‘attributes‘ tab on them, find ‘proxyaddresses‘ and add a new record prefixed with smtp:. For example, you could add ‘smtp:firstname.lastname@domain.com

But what if you need to add lots of aliases to lots of people?

You can do this with just a CSV file and the right PowerShell commands.

First, lets start off with the formatting of the CSV file. This should have two columns, one for the samaccountname of the user and another for the proxy addresses. Proxy addresses NEED to be prefixed with ‘smtp:‘, separated by a semi-colon and all lowercase.

Here’s an example:

samaccountnameProxyaddresses
usernamesmtp:name@domain.com;smtp:name2@domain.com

Next, we need to build the correct PowerShell script. I’ll list the steps of the script below followed by the script itself:

  1. Import the CSV file
  2. Run through each item in the CSV and try to add the proxy addresses
  3. Output either a success or failure message

Once you’ve built both components, you’ll need to update the CSV path in the PowerShell script.

You can now run the script for the aliases to be created. It can take a while for the entries to show, for me it’s typically 30 seconds to 5 minutes depending on the size of the CSV.

Enjoy! ?

Whitening Yellowed Keyboards

Hi everyone!

I was visiting an office a couple months back that used to be a dentist office. In the attic, we found a bunch of VERY old computer equipment. I’m fairly young so this felt like an archaeological dig.

I managed to salvage a Key Tronic keyboard with the model number E03418201D. Some of the writing on the back label is in German as well! It also had a Dell Enhanced Keyboard sticker on it.

The thing is a beauty!

However, it suffered from very yellowed plastic. Probably from smoke or UV exposure. So lets clean that thing up.

The first thing I tried was putting the keys into a jar with water and sodium bicarbonate. This involved periodically mixing the solution to make sure all the sodium didn’t settle to the bottom and putting the jar in direct sunlight. From what I understand, the sodium reacts with the UV light from the sun and produces Ozone gas which whitens the plastic.

I wouldn’t recommend the sodium bicarbonate method as I didn’t have very good results with it ?

Okay, so I moved onto using Hydrogen Peroxide. This stuff us tough to find on it’s own however, you can use hair lightener instead. I used Bblonde 40vol/12% cream peroxide.

Lets jump into it

This is what the keyboard looked like when I first brought it home. I was in love immediately…

I covered all plastic items with the cream peroxide, covered them with cling film and left them on a window ledge for a day or two depending on weather.

I found this method drastically whitened the plastics, hopefully you can see the difference from the below picture:

Enjoy! ?

Plex “Not Enough Disk Space To Convert This Item”

If you have ever installed Plex on a default installation of Ubuntu, you might have noticed that Ubuntu doesn’t take up the entire disk space for the root (‘/’) partition. This is the reason Plex has the issue described in the title.

This is VERY annoying.

I was really struggling with this issue for a good couple of weeks. When I searched for this issue, none of the results were helpful.

What I had to do: Used a live CD of gparted to increase the space of the partition and then use lvm for the partition to extend into all the new free space.

So, steps below:

Shutdown and boot into GParted live CD/ISO

Startup the GUI and extend the partition you want to expand

Close the GUI and open the GParted Terminal

Enter the LVM manager using: sudo lvm

Extend the default ubuntu vg/ubuntu-lv partition to use all available space: lvextend -l +100%FREE /dev/ubuntu-vg/ubuntu-lv

Now you can exit the LVM manager: exit

Finally, you can resize the system to use the newly sized partition: sudo resize2fs /dev/ubuntu-vg/ubuntu-lv

You can now use df -h and check that the system is using the entire space of the drive and the available partition.

You will now find that the root (‘/’) directory is no longer 99%/100% full and your media can play.

Enjoy!

One Month with Chia

So, cryptocurrency mining is NOT something I often dabble in. Maybe its the fact that using some second hand GPU to get rich off BitCoin died off a decade ago. Or maybe, it’s the fact that mining cryptocurrency often uses enough electricity to power a country… Especially with energy prices being soooo high and rising massively in my country.

So here comes Chia.

There were definitely hard drive mining schemes before Chia. One example if BurstCoin, but we won’t talk about that ?

Let me start off by saying that the plotting process takes a VERY long time. For me, I was lucky to get a plot done in 3/4 hours as my hardware is quite old. So since I already had a bunch of hard drives lying around, all I purchased to get this up and running is a SATA HBA card and some SATA power splitter cables.

This is an image of my current system as of 10th March 2022:

My very professional Chia plotting and farming rig

In that time I’ve made only around $10…#

I currently have around 30TB of plots and I am using Space Pool as solo farming isn’t a good idea at this low amount of storage.

Now, this system hasn’t been running the entire time. The Chia GUI loves to disconnect and now as NOT SYNCED for it’s own amusement. I probably lost a week or so of no mining or plotting due to this.

Am I happy with my results? Yes and no. If I was in it just for the money then definitely not. As as learning experience and trying new technologies? Yeah it has been fun.

My setup is definitely not ideal, I mean just look at the image above ?

I will try to keep releasing progress updates on my ‘farm’ as I still have a number of drives to completely plot.

Thank you for reading! Enjoy!

Removing Chat from Windows 11 Taskbar

Hi all,

quick one today! ?

How to remove the seemingly useless version of Team (called Chat) from Windows 11. Wish they allowed work and school accounts as the interface is quiet slick!

Right click the taskbar -> TaskBar Settings -> untick ‘Chat’ in the menu

It’s as simple as that! Normally, Microsoft makes it MUCH harder to remove or hide default bloatwa… sorry, software.

Some images here just for a the visual peeps!

Windows 11 TaskBar Settings
Windows 11 Chat Disabled

Enjoy! ?

Mass PST Importing using AZCopy

Ooooofff!! Been a while, man! Long time no write ??‍♂️

So, a little bit of back story – sometimes I need to import a tonne of PSTs to people O365 account for my job. I used to do this in a VERY manual way of adding their account to my Outlook and running the import. Or just giving them the PST with a guide to importing. Something needed to change!

I found a better way, I could use AZCopy to upload the files to an Azure Storage Blob and then import automatically using the built-in O365 admin tools.

First step!

Login to https://compliance.microsoft.com and head into the Information Governance -> Import section. Create a new ‘Import’ and name is as you like, not special characters though!

Select the option to upload your data, and copy the SAS URL link. The download the Azure AzCopy program. This is what we use to upload the PST files.

Second Step!

Open a command prompt or PowerShell in the same location as the AzCopy program and run the below command:

azcopy.exe copy “path to PST files” “SAS URL” –recursive

I used the recursive option as, without it, my operation wasn’t seeing the PSTs.

Leave this to run, it can take a while depending on your data. There are other parameters to this but I didn’t use them.

Third Step!

Create a PST Import mapping file – this is the step that confused me but hopefully, I can shed some light on it! Download a copy of it from here

I left the TargetRootFolder, ContentCodePage, SPFileContainer, SPManifestContainer and SPSiteURL empty. Since my data was in the following path ‘E:\Company\over_20gb’ I needed to set the FilePath to ‘over_20gb’ for all entries.

This is an example of my file:

Workload FilePath Name Mailbox IsArchive

Exchange over_20gb first.last@company.com.pst first.last@company.com FALSE

Fourth Step!

Repeat step one until the upload, tick box buttons for I’m done uploading my files AND I have access to the mapping file. Use the Select mapping file and upload your file. Following that, you can Validate.

If all is well after the validation, you can either choose to filter your data or not and start the import.

The progress of this import is visible on the Importing page. Be prepared to wait as it can take a looooong time!

Enjoy! ?

Migrating an Azure VM to HyperV or VMware

Hi All!

Today, I’ll be showing a brief overview of how I migrated some Azure VMs to an on-premise HyperV and VMware server. This is done in stages and for me, required HyperV as a middle man before moving to VMware.

First, log in to your Azure portal and find the VM that you want to migrate. Open the settings for the VM and go into the ‘Disks’

Click on the blue text that shows the VM disk name. This should open the settings for the VM disk. Head to the ‘Disk Export‘ tab and generate a new URL to download the disk. TIP: download this onto your HyperV server.

Once this has downloaded, we can head to the next step!

The download may leave a file without an extension or with a VHD extension. If needed, add the VHD extension.

We now need to use HyperV to convert the VHD file to a VHDX format. Open HyperV, use the ‘Edit Disk‘ wizard. Find the disk and use the convert option:

Once converted, create a new generation 2 VM with near-enough the same specs as the original Azure VM. Don’t create a disk with this VM. Once the VM has been created, add the new VHDX file to the SCSi controller. Make sure to untick the enable secure boot option in the VM firmware tab:

The VM should now boot as the original Azure VM ?

From here it’s easy to get the VM onto VMware. Just use the VMware converter tool on the HyperV box whilst the VM is shutdown or on the VM whilst it’s running (this method is slower).

Hope this helped someone out ?