Mount Synology NAS to Raspberry Pi Using NFS

Here’s the equipment I used, this should work similarly on other software versions:

  • Synology NAS Running DSM 7.0
  • Raspberry Pi running Raspbian 10 (Buster)

This tutorial is based on these instructions from Synology, with modifications because they didn’t work exactly as listed: https://kb.synology.com/en-uk/DSM/tutorial/How_to_access_files_on_Synology_NAS_within_the_local_network_NFS

Enable NFS on the Synology NAS:

  1. From the Synology web interface, Go to Control Panel > File Services > NFS
  2. Check Enable NFS Service. Set Maximum NFS Protocol to NFSv4.1 and click Apply.

Create NFS Permissions:

  1. Still in the Synology web interface, go to Control Panel > Shared Folder
  2. Select the folder you want to share and click Edit
  3. Click NFS Permissions > Create
    1. Enter a hostname or IP for the Raspberry Pi that will be accessing the folder.
    2. Leave Privilege on Read / Write and leave other settings at default, and click Save.
  4. Note the Mount path at the bottom of the folder edit page, then click Save again to save the folder settings.

 

 

Mount share on the Raspberry Pi:

  1. From the terminal – install nfs if not already installed:
    1. sudo apt update
    2. sudo apt install nfs-common
  2. Create the mount directory – change the path as appropriate:
    1. sudo mkdir /mnt/share
  3. Here’s where Synology instructions didn’t work. I kept getting a timeout error when trying to mount the share. This answer helped me get on the right track.
    1. To do a one time mount to ensure everything is working – replace IP address/ hostname and paths are appropriate:
      1. sudo mount -t nfs4 -o proto=tcp,port=2049 192.168.XX.XX:/volume1/share /mnt/share
    2. To mount so it persists after a reboot (thanks to this article):
      1. sudo nano /etc/fstab
      2. Add this line to the bottom, again replacing ip addresses / hostname and file paths as needed:
        1. 192.168.XX.XX:/volume1/share /mnt/share nfs defaults 0 0
      3. Exit, save the file, and reboot the pi (sudo reboot now).
      4. Once the Pi is back up, ensure the mount worked with ls /mnt/share

PowerShell Terminal: Menu Template

I created this for part of a bigger project and figured it’d make a good generic template for any PowerShell script needing a basic menu using the up and down arrow keys and enter key to make a selection.  For example data it lists the items in the root of the C: drive, but can easily be supplied any array of strings to list out in the menu. The chosen item is stored as a string in the $selection variable to be used later in the script.

<# Title: PowerShell Menu Template | Author: Michael Casey | Date: July 3rd, 2019 | Website: https://michael-casey.com/ #>

#Sample list data to populate menu:
#Set $List to any array to populate the menu with custom options
$List = Get-ChildItem -Path C:\ -Name

#menu offset to allow space to write a message above the menu
$xmin = 3
$ymin = 3

#Write Menu
Clear-Host
Write-Host ""
Write-Host "  Use the up / down arrow to navigate and Enter to make a selection"
[Console]::SetCursorPosition(0, $ymin)
foreach ($name in $List) {
    for ($i = 0; $i -lt $xmin; $i++) {
        Write-Host " " -NoNewline
    }
    Write-Host "   " + $name
}

#Highlights the selected line
function Write-Highlighted {

    [Console]::SetCursorPosition(1 + $xmin, $cursorY + $ymin)
    Write-Host ">" -BackgroundColor Yellow -ForegroundColor Black -NoNewline
    Write-Host " " + $List[$cursorY] -BackgroundColor Yellow -ForegroundColor Black
    [Console]::SetCursorPosition(0, $cursorY + $ymin)     
}

#Undoes highlight
function Write-Normal {
    [Console]::SetCursorPosition(1 + $xmin, $cursorY + $ymin)
    Write-Host "  " + $List[$cursorY]  
}

#highlight first item by default
$cursorY = 0
Write-Highlighted

$selection = ""
$menu_active = $true
while ($menu_active) {
    if ([console]::KeyAvailable) {
        $x = $Host.UI.RawUI.ReadKey()
        [Console]::SetCursorPosition(1, $cursorY)
        Write-Normal
        switch ($x.VirtualKeyCode) { 
            38 {
                #down key
                if ($cursorY -gt 0) {
                    $cursorY = $cursorY - 1
                }
            }

            40 {
                #up key
                if ($cursorY -lt $List.Length - 1) {
                    $cursorY = $cursorY + 1
                }
            }
            13 {
                #enter key
                $selection = $List[$cursorY]
                $menu_active = $false
            }
        }
        Write-Highlighted
    }
    Start-Sleep -Milliseconds 5 #Prevents CPU usage from spiking while looping
}

Clear-Host
Write-Host $selection
#May use switch statement here to process menu selection

Screenshot of the PowerShell menu template script running

Schedule Windows Notifications with PowerShell (Windows Task Scheduler / Toast Notifications)

I’ve found too many times that I can sit in front of a computer screen for several hours without taking a break but that later in the day the eye strain catches up with me. I wanted something to remind me to take a quick break every 20 minutes, following the 20-20-20 rule. There’s a lot of programs that do something like this. Workrave may be the most popular, but I’ve had a few problems with it and decided I could probably do something simpler with a scheduled PowerShell script.

Notifications

First off, I need the notification. There’s a handful of blogs on how to make a toast notification in PowerShell. There’s a PowerShell module for it, but I’d rather not have to install an external module. From the creator of that module, here’s a working example of creating a toast notification in PowerShell:


$app = '{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe'
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime]
$Template = [Windows.UI.Notifications.ToastTemplateType]::ToastImageAndText01
#Gets the Template XML so we can manipulate the values
[xml]$ToastTemplate = ([Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent($Template).GetXml())
[xml]$ToastTemplate = @"
<toast launch="app-defined-string">
<visual>
<binding template="ToastGeneric">
<text>DNS Alert…</text>
<text>We noticed that you are near Wasaki. Thomas left a 5 star rating after his last visit, do you want to try it?</text>
</binding>
</visual>
<actions>
<action activationType="background" content="Remind me later" arguments="later"/>
</actions>
</toast>
"@
$ToastXml = New-Object -TypeName Windows.Data.Xml.Dom.XmlDocument
$ToastXml.LoadXml($ToastTemplate.OuterXml)
$notify = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($app)
$notify.Show($ToastXml)

I’m just going to copy what I need and modify and condense it. For more reading on toast notifications in PowerShell this blog article and this one provide more detail, but be sure to note that since they were published, it’s become necessary to use a real app id to get the notification to show – details here.

Here’s the selection of code I’m using for the notification.

Reminder.ps1:

$app = '{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe'
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime]

$Template = [Windows.UI.Notifications.ToastTemplateType]::ToastImageAndText01

[xml]$ToastTemplate = ([Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent($Template).GetXml())

[xml]$ToastTemplate = @"
<toast launch="app-defined-string">
  <visual>
    <binding template="ToastGeneric">
      <text>Prevent Eye Strain</text>
      <text>Take a 20 second break</text>
    </binding>
  </visual>
</toast>
"@

$ToastXml = New-Object -TypeName Windows.Data.Xml.Dom.XmlDocument
$ToastXml.LoadXml($ToastTemplate.OuterXml)

$notify = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($app)

$notify.Show($ToastXml)

#source: https://gist.github.com/Windos/9aa6a684ac583e0d38a8fa68196bc2dc

Running this script will create the following notification:

VBS Launcher

The one problem with this is that when run through the task scheduler, a PowerShell console window will flash briefly before the notification displays. To avoid this intrusive popup, a short VBScript program can be used to launch the PowerShell script we’ve just created.

Launcher.vbs:

Dim shell,command
command = "powershell.exe -nologo -command ""C:\Scripts\Reminder.ps1"""
Set shell = CreateObject("WScript.Shell")
shell.Run command,0

 

Scheduling

The next step is to schedule this script to run and produce the notification every 20 minutes. This ended up being way more complex than I originally imagined. The parameters for New-ScheduledTaskTrigger don’t allow setting a RepetitionInterval with a Daily or AtLogOn parameter, which makes doing this a little tricky. In the end, I took some inspiration from this solution. The best solution simply seemed to be to set up the task I want in the Windows Task Scheduler GUI, export the XML, and then use that in the PowerShell script. At this point I’m not really doing much in PowerShell itself to create the task, however, ultimately this still creates a code-based solution that can easily be put into other scripts or run on a server without a GUI.

After creating the task in Windows Task Scheduler, I ran

Export-ScheduledTask "BreakReminder"

in PowerShell and get the following XML:

<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo>
    <Description>A reminder every 20 minutes to take a 20 second break to prevent eye strain.</Description>
    <URI>\BreakReminder</URI>
  </RegistrationInfo>
  <Settings>
    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
    <IdleSettings>
      <StopOnIdleEnd>true</StopOnIdleEnd>
      <RestartOnIdle>false</RestartOnIdle>
    </IdleSettings>
  </Settings>
  <Triggers>
    <CalendarTrigger>
      <StartBoundary>2019-06-10T00:00:00</StartBoundary>
      <Repetition>
        <Interval>PT20M</Interval>
        <Duration>P1D</Duration>
        <StopAtDurationEnd>true</StopAtDurationEnd>
      </Repetition>
      <ScheduleByDay>
        <DaysInterval>1</DaysInterval>
      </ScheduleByDay>
    </CalendarTrigger>
  </Triggers>
  <Actions Context="Author">
    <Exec>
      <Command>C:\Scripts\Launcher.vbs</Command>
    </Exec>
  </Actions>
</Task>

I then squeezed that all into one line and end up with the following very messy lines of PowerShell code that can be used to create the task from scratch.  The Reminder.ps1 and Launcher.vbs files from above just need to be saved somewhere and the file paths in Launcher.vbs and the below code need to be updated accordingly.

$TaskXML = '<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task"> <RegistrationInfo> <Description>A reminder every 20 minutes to take a 20 second break to prevent eye strain.</Description> <URI>\BreakReminder</URI> </RegistrationInfo> <Triggers> <CalendarTrigger> <Repetition> <Interval>PT20M</Interval> <Duration>P1D</Duration> <StopAtDurationEnd>true</StopAtDurationEnd> </Repetition> <StartBoundary>2019-06-10T00:00:00</StartBoundary> <Enabled>true</Enabled> <ScheduleByDay> <DaysInterval>1</DaysInterval> </ScheduleByDay> </CalendarTrigger> </Triggers> <Settings> <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy> <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries> <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries> <AllowHardTerminate>true</AllowHardTerminate> <StartWhenAvailable>false</StartWhenAvailable> <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable> <IdleSettings> <StopOnIdleEnd>true</StopOnIdleEnd> <RestartOnIdle>false</RestartOnIdle> </IdleSettings> <AllowStartOnDemand>true</AllowStartOnDemand> <Enabled>true</Enabled> <Hidden>false</Hidden> <RunOnlyIfIdle>false</RunOnlyIfIdle> <WakeToRun>false</WakeToRun> <ExecutionTimeLimit>P3D</ExecutionTimeLimit> <Priority>7</Priority> </Settings> <Actions Context="Author"> <Exec> <Command>C:\Scripts\Launcher.vbs</Command> </Exec> </Actions> </Task>'

Register-ScheduledTask "BreakReminder" -Xml $TaskXML.OuterXml

Raspberry Pi: Schedule Print Job

After having a couple sets of ink cartridges dry up on me after extended non-use, I learned that I’d need to print something regularly to keep my cheap inkjet printer working. After ignoring a few of my own reminders to print a test page manually, I decided to create an automated solution using a cron job on a Raspberry Pi I already have running on my home network.

To send the file to the printer, I used CUPS (documentation here or here). I followed the tutorial here to get it installed and activate the web interface.

With the printer added, I ran lpstat -p -d to get the exact name of the printer, then lp -d <printer> <file> to send a file to the printer.

To add the weekly cron job, I ran crontab -e and added the following line to get it to print weekly at noon on Sundays:

0 12 * * Sun lp -d HP_DeskJet_printer Sample.pdf

 

Raspberry Pi – Minimal Headless Setup

For my own quick reference, mostly taken from here:

  1. See end of article if SD card was previously used.
  2. Download Raspbian from https://www.raspberrypi.org/downloads/raspbian/
  3. Download and install Win32DiskImager from https://sourceforge.net/projects/win32diskimager/
  4. Using Win32 Disk Imager, write the ISO to the SD card
  5. Add a file called wpa_supplicant.conf in the boot drive with the following (adding your own SSID and password):
    ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
    update_config=1
    country=US
    
    network={
    ssid="$SSID"
    psk="$Password"
    key_mgmt=WPA-PSK
    }
  6. Add a blank file named ssh (with no file extension) to the boot drive to enable ssh. In Windows notepad or similar, save the file as “ssh” (with the quote marks) to avoid it adding a .txt file extension.
  7. Insert the SD card to the Raspberry Pi, and plug in to power. Wait a few seconds, then use the Fing app to get its IP address on a home network, and use Putty to SSH in using default credentials of pi / raspberry.
  8. Change the default password by running
    passwd
Edit: Make Raspberry Pi Email You Its IP Address
As mentioned above, I’ve previously used the Fing app to find the IP address of the Pi on my home network. Recently, I’ve needed to access the Pi on a university network and wanted to do so without connecting it to a monitor and keyboard / mouse. Here’s a script to email the IP address of the Pi to myself every time it’s booted. Just modify the email and password (I’m using a secondary email account so I don’t care about the password being stored in plain text):
#pip install netifaces
import pip
def install(package):
if hasattr(pip, 'main'):
pip.main(['install', package])
else:
pip._internal.main(['install', package])
if __name__ == '__main__':
install('netifaces')

import smtplib
import netifaces as ni
import socket

ni.ifaddresses('wlan0')
ip = ni.ifaddresses('wlan0')[ni.AF_INET][0]['addr']
hostname = socket.gethostname()
message = hostname + "\n" + ip

fromaddr = 'myemail@gmail.com'
toaddrs = 'myemail@gmail.com'
username = 'myemail@gmail.com'
password = 'password'
server = smtplib.SMTP('smtp.gmail.com', '587')
server.ehlo()
server.starttls()
server.login(username,password)
server.sendmail(fromaddr, toaddrs, message)
server.quit()

Add the following to /etc/rc.local after the line “# By default this script does nothing.” and point it to the script you just saved:

sleep 20
python /path/to/script.py

If the SD card was previously used in a Raspberry Pi, the partitions will need to be merged again.

  1. Open Disk Management.
  2. Right click and select Delete Volume for both partitions on the SD card.
  3. Right click and select New Simple Volume to create one partition for the whole SD card. Make sure the partition is FAT32. Win32DiskImager will create two partitions again, but this way it initially sees the whole SD card.

 

Ubuntu: Resume Script in Terminal After Reboot

I needed to reboot in the middle of my shell script to install Kubernetes / Brigade, and my Googling found several people with similar questions, but no answers that quite worked for me. Here’s the basic code just to get a shell script to reboot and then resume in a visible terminal after the reboot.

The commands to be run after the reboot are echoed into a new shell script, so characters like slashes and dollar signs need to be escaped with a backslash.

Most of the posts with similar questions mentioned that running a shell script with sudo is a bad idea. Since my script is designed to be run in a sandboxed VM and most of the commands needed sudo permissions anyways, I figured it’s not a problem in this case.

#after reboot
echo "echo \"Post-reboot content goes here\"

#remove this from running at startup
rm ~/.config/autostart/myScript.desktop
rm /home/$SUDO_USER/myScript.sh" >> /home/$SUDO_USER/myScript.sh

#make file executable
chmod +x ~/myScript.sh

#set new script to run after reboot
cd .config/
mkdir -p "autostart/"
cd ..
echo "[Desktop Entry]
Type=Application
Exec=x-terminal-emulator -e sudo /home/$SUDO_USER/myScript.sh
Name=Testing" >> ~/.config/autostart/myScript.desktop

#before reboot
echo "Pre-reboot commands go here"
sleep 30

reboot -f

Running the script:
 After the reboot:

Self-Elevating PowerShell and Batch Scripts

Many scripts require admin permissions, but remembering to right click and select “Run as Administrator” can be a pain. Fortunately, both batch and PowerShell scripts can written so that they’ll prompt for admin permissions if not run as an administrator. This won’t get around not having administrator permissions, but will save you a couple clicks every run by jumping straight to the UAC prompt.

Neither of these scripts are mine, but I’m documenting them here for my own quick and frequent reference.

CMD / Batch scripts:

::::::::::::::::::::::::::::::::::::::::::::
:: Elevate.cmd - Version 4
:: Automatically check & get admin rights
:: see "https://stackoverflow.com/a/12264592/1016343" for description
::::::::::::::::::::::::::::::::::::::::::::
 @echo off
 CLS
 ECHO.
 ECHO =============================
 ECHO Running Admin shell
 ECHO =============================

:init
 setlocal DisableDelayedExpansion
 set cmdInvoke=1
 set winSysFolder=System32
 set "batchPath=%~0"
 for %%k in (%0) do set batchName=%%~nk
 set "vbsGetPrivileges=%temp%\OEgetPriv_%batchName%.vbs"
 setlocal EnableDelayedExpansion

:checkPrivileges
  NET FILE 1>NUL 2>NUL
  if '%errorlevel%' == '0' ( goto gotPrivileges ) else ( goto getPrivileges )

:getPrivileges
  if '%1'=='ELEV' (echo ELEV & shift /1 & goto gotPrivileges)
  ECHO.
  ECHO **************************************
  ECHO Invoking UAC for Privilege Escalation
  ECHO **************************************

  ECHO Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%"
  ECHO args = "ELEV " >> "%vbsGetPrivileges%"
  ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%"
  ECHO args = args ^& strArg ^& " "  >> "%vbsGetPrivileges%"
  ECHO Next >> "%vbsGetPrivileges%"

  if '%cmdInvoke%'=='1' goto InvokeCmd 

  ECHO UAC.ShellExecute "!batchPath!", args, "", "runas", 1 >> "%vbsGetPrivileges%"
  goto ExecElevation

:InvokeCmd
  ECHO args = "/c """ + "!batchPath!" + """ " + args >> "%vbsGetPrivileges%"
  ECHO UAC.ShellExecute "%SystemRoot%\%winSysFolder%\cmd.exe", args, "", "runas", 1 >> "%vbsGetPrivileges%"

:ExecElevation
 "%SystemRoot%\%winSysFolder%\WScript.exe" "%vbsGetPrivileges%" %*
 exit /B

:gotPrivileges
 setlocal & cd /d %~dp0
 if '%1'=='ELEV' (del "%vbsGetPrivileges%" 1>nul 2>nul  &  shift /1)

 ::::::::::::::::::::::::::::
 ::START
 ::::::::::::::::::::::::::::
 REM Run shell as admin (example) - put here code as you like
 ECHO %batchName% Arguments: P1=%1 P2=%2 P3=%3 P4=%4 P5=%5 P6=%6 P7=%7 P8=%8 P9=%9
 REM put remaining commands here
 cmd /k

source (stackoverflow)

PowerShell:

Fortunately this one’s a lot shorter:

If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))

{   
	$arguments = "&amp; '" + $myinvocation.mycommand.definition + "'"
	Start-Process powershell -Verb runAs -ArgumentList $arguments
	Break
}

source (stackoverflow)

PowerShell: Ping Range of IP Addresses

The other day I needed to scan a small section of our network which uses static IP addresses rather than DHCP to see which assigned IP addresses actually correlated with a working device and which were no longer being used by an active device. Though there are plenty of fancy tools for this, due to the simplicity of what I wanted I figured it would be easier to throw together a quick PowerShell script than install and learn a third-party tool.

Here’s what I came up with:

$Failure = @() #initialize array

#pings IP addresses from 192.168.1.0 to 192.168.1.255
for ($i=0; $i -le 255; $i++)
{
	$IP = "192.168.1." + $i #append the iterator as the last byte of the IP address
	if(!(Test-Connection -ComputerName $IP -Quiet -Count 2)){ #if noting responds to the ping, add the IP address to an array
        $Failure += $IP
	}
}

Clear-Host

foreach ($site in $Failure) {
    Write-Host $site -ForegroundColor red #iterate through and write results from array
}

Note that this runs really slowly, but in this case there wasn’t any rush, so I could let it run in the background for a while.

Domain User Account Info

This is a short but incredibly useful command line command I’ve found for pulling information on domain accounts in an Active Directory environment:

net user [username] /domain

The net user command is designed to create / update domain accounts, but in this form just displays a ton of info that can be useful to IT, like when a user’s password expires, what active directory groups they’re a member of, etc. There’s plenty of other commands to do similar things, but this one is an easy way to get a readout of a ton of information.

Full documentation is available on Microsoft’s website.

PowerShell: Starting Remote Desktop Once Server Available

This is a simple but useful bit of PowerShell. Have you ever been waiting on a server to reboot and ended up pinging it or trying to start a remote desktop session several times before it’s back up and you can log in? This script pings a server (or any computer) every 15 seconds until the server responds, then starts a remote desktop session.

param ([Parameter(Mandatory=$true)][string]$URL)#get the parameter and set it as the variable $URL

While (!(Test-Connection -ComputerName $URL -Quiet -Count 1)) { #Loops until Test-Connection reaches the server and returns true. Only tries one ping at a time
	Start-Sleep -s 15 #pauses for 15 seconds. Easily editable to different time lengths
}

mstsc /v:$URL #starts a remote desktop session

To use it, just save and run the script, passing the hostname or IP address as a parameter:

PowerShell screenshot showing passing an IP address as a parameter.

And if you want it as a one-liner you can copy and paste rather than saving it, use the following and replace the IP address at the beginning with the target IP address or hostname:

$URL = "192.168.0.3"; While (!(Test-Connection -ComputerName $URL -Quiet -Count 1)) { Start-Sleep -s 15 } ; mstsc /v:$URL