
[{"content":"This script requires that clipman, rofi, and dmenu is installed. All you need to do is change the location of the clipman history file and the name/location of the rofi theme file. As part of your niri configuration, add the following line to use clipman: spawn-sh-at-startup \u0026ldquo;wl-paste -t text \u0026ndash;watch clipman store \u0026ndash;no-persist\u0026rdquo; The screenshot below shows the main rofi window which displays all relevant options. The first option allows you to quickly paste the most recently saved item in the clipboard and, for conveneince, the entire clip is not displayed. The delete option allows you to delete any individual item from the clipboard, giving you quite a bit of flexibility.\nThe screenshot below shows the delete window. When you press the enter key, the selected item is deleted from the clipboard and the window is displayed again. This allows you to delete as many items as you need to.\n#!/bin/bash FILE=\u0026#34;/home/karmst/.local/share/clipman.json\u0026#34; ROFI_THEME=\u0026#34;fzf_finder.rasi\u0026#34; ARGS=\u0026#34;-config ~/.config/rofi/themes/$ROFI_THEME -dmenu\u0026#34; LINE_COUNT_1=\u0026#34;-l 6\u0026#34; LINE_COUNT_2=\u0026#34;-l 25\u0026#34; COUNT=$(cat $FILE | jq \u0026#34;.[] | length\u0026#34; | wc -l) CURRENT=$(cat $FILE | jq \u0026#34;.[($COUNT-1)]\u0026#34;) R_PROMPT=\u0026#34;Clip Manager [clip count is $COUNT] \u0026#34; D_EMPTY=\u0026#34;Nothing to see here because the item count is $COUNT\u0026#34; function list () { case \u0026#34;$choice\u0026#34; in \u0026#34;$CURRENT\u0026#34;) cmd=$(clipman restore \u0026amp;\u0026amp; wtype -M ctrl -M shift v) ;; Copy) cmd=$(clipman pick -t rofi --tool-args=\u0026#34;$ARGS $LINE_COUNT_2\u0026#34; --err-on-no-selection \u0026amp;\u0026amp; wtype -M ctrl -M shift c) ;; Paste) cmd=$(clipman pick -t rofi --tool-args=\u0026#34;$ARGS $LINE_COUNT_2\u0026#34; --err-on-no-selection \u0026amp;\u0026amp; wtype -M ctrl -M shift v) ;; List) cmd=$(clipman pick -t rofi --tool-args=\u0026#34;$ARGS $LINE_COUNT_2\u0026#34; --err-on-no-selection) ;; Delete) old=1 new=0 while [ $new -lt $old ] do old=$(cat $FILE | jq \u0026#34;.[] | length\u0026#34; | wc -l) cmd=$(clipman clear -t rofi --tool-args=\u0026#34;$ARGS $LINE_COUNT_2\u0026#34;) new=$(cat $FILE | jq \u0026#34;.[] | length\u0026#34; | wc -l) done ;; Wipe) cmd=$(clipman clear -a) ;; esac } function display_first () { if [[ -f \u0026#34;$FILE\u0026#34; ]]; then choice=$(echo -e \u0026#34;$CURRENT\\nPaste\\nCopy\\nList\\nDelete\\nWipe\u0026#34; | rofi -p \u0026#34;$R_PROMPT\u0026#34; $ARGS $LINE_COUNT_1 -theme-str \u0026#39;listview { scrollbar: false; }\u0026#39;) list else dunstify -t 2500 \u0026#34;$D_EMPTY\u0026#34; fi } display_first RET=$? while [ $RET -eq 1 ] do display_first RET=$? done exit 0 ","date":"1 May 2026","externalUrl":null,"permalink":"/series/niri/clipman/","section":"Series","summary":"This script requires that clipman, rofi, and dmenu is installed. All you need to do is change the location of the clipman history file and the name/location of the rofi theme file. As part of your niri configuration, add the following line to use clipman: spawn-sh-at-startup “wl-paste -t text –watch clipman store –no-persist” The screenshot below shows the main rofi window which displays all relevant options. The first option allows you to quickly paste the most recently saved item in the clipboard and, for conveneince, the entire clip is not displayed. The delete option allows you to delete any individual item from the clipboard, giving you quite a bit of flexibility.\n","title":"Clipboard Manager","type":"series"},{"content":" What it does # Even though the same functionality can be implemented with rofi and the window mode, I wanted to write my own version with the following additions:\nDisplays the Niri workspace on which the window is open The currently focused window is prefixed with an asterisk If no other window is open, an alert is displayed and the script exits The rofi prompt displays the total number of open windows Please ensure that you have either rofi or fuzzel installed, depending on the configuration. If using rofi you can change the theme file, the prompt, and the number of lines shown. If using fuzzel you can change the font family, the font size, and the window width. You can also change the length of time that the dunstify notification is shown for. #!/bin/bash WINS=\u0026#34;$(niri msg -j windows | jq \u0026#34;[.[] | select(.is_urgent == false|true )] | length\u0026#34;)\u0026#34; D_TIMER=2000 if [[ $WINS -eq 0 ]]; then dunstify \u0026#34;There are no other windows to display\u0026#34; -t $D_TIMER exit 0 fi R_PROMPT=\u0026#34;Window List ($WINS open) \u0026#34; R_CONFIG=\u0026#34;/home/karmst/.config/rofi/themes/fzf_finder.rasi\u0026#34; R_LINES=12 F_WIDTH=108 F_FONT=\u0026#34;ComicSansMS\u0026#34; F_FONT=\u0026#34;ComicNeue-Italic\u0026#34; F_FONT=\u0026#34;Monospace\u0026#34; F_SIZE=10 F_FONTCONFIG=\u0026#34;$F_FONT:size=$F_SIZE\u0026#34; RUNNER=\u0026#34;rofi -dmenu -i -l \u0026#34;$R_LINES\u0026#34; -p \u0026#34;$R_PROMPT\u0026#34; -config \u0026#34;$R_CONFIG\u0026#34; -theme-str \u0026#39;window {width: 80%;} listview { scrollbar: false; }\u0026#39;\u0026#34; RUNNER=\u0026#34;fuzzel -d -w $F_WIDTH -f $F_FONTCONFIG\u0026#34; options=() for ((index=0; index\u0026lt;WINS; index++)); do ID=$(niri msg -j windows | jq \u0026#34;.[$index]\u0026#34; | jq \u0026#34;.id\u0026#34;) TITLE=$(niri msg -j windows | jq \u0026#34;.[$index]\u0026#34; | jq \u0026#34;.title\u0026#34; | sed \u0026#39;s/\\\u0026#34;\\(disabled.\\+\\)\u0026#34;/\\1/\u0026#39;) WS=$(niri msg -j windows | jq \u0026#34;.[$index]\u0026#34; | jq \u0026#34;.workspace_id\u0026#34;) WS_NAME=\u0026#34;$(niri msg -j workspaces | jq \u0026#34;.[] | select(.id == $WS)\u0026#34; | jq \u0026#34;.name\u0026#34; | sed \u0026#39;s/\\\u0026#34;\\(.\\+\\)\u0026#34;/\\1/\u0026#39;)\u0026#34; WIN_COUNT=\u0026#34;$(niri msg -j windows | jq \u0026#34;[.[] | select(.workspace_id == $WS)] | length\u0026#34;)\u0026#34; IS_FOCUSED=$(niri msg -j windows | jq \u0026#34;.[$index]\u0026#34; | jq \u0026#34;.is_focused\u0026#34;) FOCUSED=\u0026#34; \u0026#34; if [[ $IS_FOCUSED == true ]]; then FOCUSED=\u0026#34;* \u0026#34; fi CONCAT=\u0026#34;$FOCUSED[$WIN_COUNT : $WS_NAME] : $TITLE#~#$ID\u0026#34; options+=(\u0026#34;$CONCAT\u0026#34;) done if [[ $WINS -eq 1 ]]; then cmd=$(niri msg action focus-window --id \u0026#34;$ID\u0026#34;) exit 0 fi get_options() { for opt in \u0026#34;${options[@]}\u0026#34;; do echo \u0026#34;${opt%%#~#*}\u0026#34; done } selection=$(get_options | $RUNNER) if [[ -z \u0026#34;$selection\u0026#34; ]]; then exit 0 fi action=\u0026#34;\u0026#34; for opt in \u0026#34;${options[@]}\u0026#34;; do display_name=\u0026#34;${opt%%#~#*}\u0026#34; if [[ \u0026#34;$selection\u0026#34; == \u0026#34;$display_name\u0026#34; ]]; then action=\u0026#34;${opt##*#~#}\u0026#34; break fi done if [[ -n \u0026#34;$action\u0026#34; ]]; then cmd=$(niri msg action focus-window --id \u0026#34;$action\u0026#34;) fi exit 0 ","date":"12 May 2026","externalUrl":null,"permalink":"/series/niri/window_switcher/","section":"Series","summary":"What it does # Even though the same functionality can be implemented with rofi and the window mode, I wanted to write my own version with the following additions:\n","title":"Window switcher","type":"series"},{"content":"When integrated with Waybar, this script returns the number of OS updates. As an example, the returned text below shows that no updates are available. {\u0026ldquo;text\u0026rdquo;:\u0026ldquo; \u0026ldquo;,\u0026ldquo;tooltip\u0026rdquo;:\u0026ldquo;No updates\u0026rdquo;,\u0026ldquo;class\u0026rdquo;:\u0026ldquo;unmuted\u0026rdquo;}\n!/usr/bin/env bash OS=$(cat /etc/os-release | grep \u0026#34;^NAME\u0026#34; | sed -E \u0026#39;s/^NAME=(\u0026#34;?)(.+)(\u0026#34;?)/\\2/\u0026#39; | tail -1) NUM=0; if [[ $OS =~ \u0026#34;Void\u0026#34; ]]; then NUM=$(xbps-install -nuM | wc -l) elif [[ $OS == \u0026#34;ArcoLinux\u0026#34; ]]; then NUM=$(checkupdates | wc -l) fi if [ \u0026#34;$NUM\u0026#34; -gt 0 ]; then echo \u0026#39;{\u0026#34;text\u0026#34;:\u0026#34;{\u0026#34; \u0026#39;$NUM\u0026#39;\u0026#34;,\u0026#34;tooltip\u0026#34;:\u0026#34;Updates available\u0026#34;,\u0026#34;class\u0026#34;:\u0026#34;unmuted\u0026#34;}\u0026#39; else echo \u0026#39;{\u0026#34;text\u0026#34;:\u0026#34; \u0026#34;,\u0026#34;tooltip\u0026#34;:\u0026#34;No updates\u0026#34;,\u0026#34;class\u0026#34;:\u0026#34;unmuted\u0026#34;}\u0026#39; fi ","date":"1 May 2026","externalUrl":null,"permalink":"/series/niri/updates/","section":"Series","summary":"When integrated with Waybar, this script returns the number of OS updates. As an example, the returned text below shows that no updates are available. {“text”:“ “,“tooltip”:“No updates”,“class”:“unmuted”}\n","title":"Display OS updates","type":"series"},{"content":"This script allows one to enable and disable the touchpad on niri, which relies on the niri configuration file having either of the values shown below. //off // touchpad enabled off // touchpad disabled The script does a grep for touchpad enabled and acts accordingly.\n#!/bin/bash FILE=\u0026#34;/home/karmst/.config/niri/input.kdl\u0026#34; # cmd=$(grep -E \u0026#34;touchpad enabled\u0026#34; $FILE) cmd=$(grep -zoP \u0026#34;.+touchpad.+\\n.+\\/\\/off\u0026#34; $FILE) echo $cmd if [[ $cmd =~ ^.+ ]]; then sed -i -e \u0026#39;s/\\/\\/off \\/\\/ touchpad enabled/off \\/\\/ touchpad disabled/g\u0026#39; $FILE dunstify \u0026#34;Touchpad is disabled\u0026#34; else sed -i -e \u0026#39;s/off \\/\\/ touchpad disabled/\\/\\/off \\/\\/ touchpad enabled/g\u0026#39; $FILE dunstify \u0026#34;Touchpad is enabled\u0026#34; fi ","date":"2 May 2026","externalUrl":null,"permalink":"/series/niri/touchpad/","section":"Series","summary":"This script allows one to enable and disable the touchpad on niri, which relies on the niri configuration file having either of the values shown below. //off // touchpad enabled off // touchpad disabled The script does a grep for touchpad enabled and acts accordingly.\n","title":"Touchpad toggle","type":"series"},{"content":"This script will generate a password using a pick list in Rofi, and it is written to run on the Wayland compositor.\n#!/bin/bash function d_program() { { printf \u0026#39;%s\\n\u0026#39; \u0026#34;16 non complex\u0026#34; printf \u0026#39;%s\\n\u0026#39; \u0026#34;24 non complex\u0026#34; printf \u0026#39;%s\\n\u0026#39; \u0026#34;32 non complex\u0026#34; printf \u0026#39;%s\\n\u0026#39; \u0026#34;64 non complex\u0026#34; printf \u0026#39;%s\\n\u0026#39; \u0026#34;96 non complex\u0026#34; printf \u0026#39;%s\\n\u0026#39; \u0026#34;16 complex\u0026#34; printf \u0026#39;%s\\n\u0026#39; \u0026#34;24 complex\u0026#34; printf \u0026#39;%s\\n\u0026#39; \u0026#34;32 complex\u0026#34; printf \u0026#39;%s\\n\u0026#39; \u0026#34;64 complex\u0026#34; printf \u0026#39;%s\\n\u0026#39; \u0026#34;96 complex\u0026#34; } | dmenu -l 20 -c -b -fn \u0026#39;SauceCodePro Nerd Font-16\u0026#39; -sb \u0026#34;#cd1bcb\u0026#34; -h 30 -bw 3 -i -p \u0026#39;\u0026#39; ${dmenu_args[@]} } choice=$(d_program) if [[ $choice =~ ^$ ]]; then exit 0 fi chars=\u0026#34;a-zA-Z0-9\u0026#34; length=$(echo $choice | sed \u0026#39;s/\\([0-9][0-9]\\)\\(.\\+\\)/\\1/\u0026#39;) type=$(echo $choice | sed \u0026#39;s/\\([0-9][0-9]\\)\\(.\\+\\)/\\2/\u0026#39;) if [[ type =~ ^complex$ ]]; then chars=\u0026#34; a-zA-Z0-9!#$%\u0026amp;()*+-:?@^_{|}~\u0026#34; fi pass=$(tr -dc $chars \u0026lt; /dev/urandom | head -c $length) copied=$(echo -n \u0026#34;$pass\u0026#34; | tr -d \u0026#39;\\n\u0026#39; | wl-copy) notify-send \u0026#34;Generated password [$length] : \u0026#34;$pass --expire-time=2500 exit 0 ","date":"4 May 2026","externalUrl":null,"permalink":"/series/niri/passwords/","section":"Series","summary":"This script will generate a password using a pick list in Rofi, and it is written to run on the Wayland compositor.\n#!/bin/bash function d_program() { { printf '%s\\n' \"16 non complex\" printf '%s\\n' \"24 non complex\" printf '%s\\n' \"32 non complex\" printf '%s\\n' \"64 non complex\" printf '%s\\n' \"96 non complex\" printf '%s\\n' \"16 complex\" printf '%s\\n' \"24 complex\" printf '%s\\n' \"32 complex\" printf '%s\\n' \"64 complex\" printf '%s\\n' \"96 complex\" } | dmenu -l 20 -c -b -fn 'SauceCodePro Nerd Font-16' -sb \"#cd1bcb\" -h 30 -bw 3 -i -p '' ${dmenu_args[@]} } choice=$(d_program) if [[ $choice =~ ^$ ]]; then exit 0 fi chars=\"a-zA-Z0-9\" length=$(echo $choice | sed 's/\\([0-9][0-9]\\)\\(.\\+\\)/\\1/') type=$(echo $choice | sed 's/\\([0-9][0-9]\\)\\(.\\+\\)/\\2/') if [[ type =~ ^complex$ ]]; then chars=\" a-zA-Z0-9!#$%\u0026()*+-:?@^_{|}~\" fi pass=$(tr -dc $chars \u003c /dev/urandom | head -c $length) copied=$(echo -n \"$pass\" | tr -d '\\n' | wl-copy) notify-send \"Generated password [$length] : \"$pass --expire-time=2500 exit 0","title":"Password generator","type":"series"},{"content":"When integrated with Waybar, this script returns the total number of Windows on all workspaces. As an example, the returned text below shows that there are 3 windows on 5 workspaces. {\u0026ldquo;text\u0026rdquo;:\u0026ldquo;3 on 5 \u0026ldquo;,\u0026ldquo;tooltip\u0026rdquo;:\u0026ldquo;Window Count\u0026rdquo;,\u0026ldquo;class\u0026rdquo;:\u0026ldquo;unmuted\u0026rdquo;}\n#!/bin/bash WS=\u0026#34;$(niri msg -j workspaces | jq \u0026#34;[.[] | select(.is_urgent == false|true )] | length\u0026#34;)\u0026#34; NUM=\u0026#34;$(niri msg -j windows | jq \u0026#34;[.[] | select(.is_focused == false|true )] | length\u0026#34;)\u0026#34; if [[ $NUM -gt 0 ]] then TEXT=$NUM\u0026#34; on \u0026#34;$WS else TEXT=$WS fi echo \u0026#39;{\u0026#34;text\u0026#34;:\u0026#34;\u0026#39;$TEXT\u0026#39; \u0026#34;,\u0026#34;tooltip\u0026#34;:\u0026#34;Updates available\u0026#34;,\u0026#34;class\u0026#34;:\u0026#34;unmuted\u0026#34;}\u0026#39; ","date":"1 May 2026","externalUrl":null,"permalink":"/series/niri/window_count_total/","section":"Series","summary":"When integrated with Waybar, this script returns the total number of Windows on all workspaces. As an example, the returned text below shows that there are 3 windows on 5 workspaces. {“text”:“3 on 5 “,“tooltip”:“Window Count”,“class”:“unmuted”}\n","title":"Total window count","type":"series"},{"content":"This script will cycle through all workspaces, and will wrap to the first workspace when the last workspace is reached or will wrap to the last workspace when the first workspace is reached. This allows for seamless navigation of workspaces.\n#!/bin/bash current=\u0026#34;$(niri msg -j workspaces | jq \u0026#34;.[] | select(.is_focused == true ) | .idx\u0026#34;)\u0026#34; count=\u0026#34;$(niri msg -j workspaces | jq \u0026#34;[.[] | select(.is_urgent == false|true )] | length\u0026#34;)\u0026#34; moveTo=0; if [[ $@ == \u0026#39;right\u0026#39; ]] then moveTo=$(($current+1)) if [[ $moveTo -gt $count ]] then moveTo=1 fi fi if [[ $@ == \u0026#39;left\u0026#39; ]] then moveTo=$(($current-1)) if [[ $moveTo -eq 0 ]] then moveTo=$count fi fi move=$(niri msg action focus-workspace $moveTo) ","date":"1 May 2026","externalUrl":null,"permalink":"/series/niri/window_move/","section":"Series","summary":"This script will cycle through all workspaces, and will wrap to the first workspace when the last workspace is reached or will wrap to the last workspace when the first workspace is reached. This allows for seamless navigation of workspaces.\n","title":"Workspace cycle","type":"series"},{"content":"When integrated with Waybar, this script returns the currently selected Window followed by the total number of Windows on the current workspace. As an example, the returned text below shows that the currently selected window is the first window on a workspace with two windows. {\u0026ldquo;text\u0026rdquo;:\u0026ldquo; 1:2 \u0026ldquo;,\u0026ldquo;tooltip\u0026rdquo;:\u0026ldquo;Current Window\u0026rdquo;,\u0026ldquo;class\u0026rdquo;:\u0026ldquo;unmuted\u0026rdquo;}\n#!/bin/bash ME=\u0026#34;$(niri msg -j workspaces | jq \u0026#34;.[] | select(.is_focused == true ) | .id\u0026#34;)\u0026#34; NUM=\u0026#34;$(niri msg -j windows | jq \u0026#34;[.[] | select(.workspace_id == $ME)] | length\u0026#34;)\u0026#34; LAYOUT=\u0026#34;$(niri msg -j windows | jq \u0026#34;.[] | select(.is_focused == true ) | .layout\u0026#34;)\u0026#34; POS_IN_LAYOUT=$(echo $LAYOUT | jq \u0026#34;. | .pos_in_scrolling_layout\u0026#34;) FINAL=$(echo $POS_IN_LAYOUT | sed -E \u0026#39;s/^\\[ (.+?),(.+)/\\1/\u0026#39;) if [[ $NUM -gt 0 \u0026amp;\u0026amp; $FINAL -gt 0 ]] then TEXT=\u0026#34; \u0026#34;$FINAL\u0026#34;:\u0026#34;$NUM fi echo \u0026#39;{\u0026#34;text\u0026#34;:\u0026#34;\u0026#39;$TEXT\u0026#39; \u0026#34;,\u0026#34;tooltip\u0026#34;:\u0026#34;Current Window\u0026#34;,\u0026#34;class\u0026#34;:\u0026#34;unmuted\u0026#34;}\u0026#39; ","date":"1 May 2026","externalUrl":null,"permalink":"/series/niri/window_count_workspace/","section":"Series","summary":"When integrated with Waybar, this script returns the currently selected Window followed by the total number of Windows on the current workspace. As an example, the returned text below shows that the currently selected window is the first window on a workspace with two windows. {“text”:“ 1:2 “,“tooltip”:“Current Window”,“class”:“unmuted”}\n","title":"Workspace window count","type":"series"},{"content":"","date":"12 May 2026","externalUrl":null,"permalink":"/tags/linux/","section":"Tag","summary":"","title":"Linux","type":"tags"},{"content":"Posts related to the Niri Window Manager on Linux.\n","date":"12 May 2026","externalUrl":null,"permalink":"/series/niri/","section":"Series","summary":"Posts related to the Niri Window Manager on Linux.\n","title":"Niri Window Manager","type":"series"},{"content":"","date":"12 May 2026","externalUrl":null,"permalink":"/tags/scripting/","section":"Tag","summary":"","title":"Scripting","type":"tags"},{"content":" This section lists all the Series that Belkast Consulting has compiled over the years. ","date":"12 May 2026","externalUrl":null,"permalink":"/series/","section":"Series","summary":" This section lists all the Series that Belkast Consulting has compiled over the years. ","title":"Series","type":"series"},{"content":" ","date":"12 May 2026","externalUrl":null,"permalink":"/tags/","section":"Tag","summary":"","title":"Tag","type":"tags"},{"content":"First, you can run acpi_listen to get the name of the key that is pressed.\nacpid configuration # Directory # /etc/acpid/events\nFiles # Volume Down # event=button/volumedown action=sudo -u karmst XDG_RUNTIME_DIR=/run/user/1000 /home/karmst/scripts/vol_down.sh Volume Up # event=button/volumeup action=sudo -u karmst XDG_RUNTIME_DIR=/run/user/1000 /home/karmst/scripts/vol_up.sh Volume Mute # event=button/mute action=sudo -u karmst XDG_RUNTIME_DIR=/run/user/1000 pamixer --toggle-mute ","date":"3 April 2026","externalUrl":null,"permalink":"/posts/linux/acpid/","section":"Posts","summary":"First, you can run acpi_listen to get the name of the key that is pressed.\nacpid configuration # Directory # /etc/acpid/events\n","title":"Configure acpid events","type":"posts"},{"content":"","date":"3 April 2026","externalUrl":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"#!/bin/bash SCRIPT_DIR=$( cd -- \u0026#34;$( dirname -- \u0026#34;${BASH_SOURCE[0]}\u0026#34; )\u0026#34; \u0026amp;\u0026gt; /dev/null \u0026amp;\u0026amp; pwd ) dir_root=$SCRIPT_DIR\u0026#34;/\u0026#34; zip_output=$SCRIPT_DIR\u0026#34;/\u0026#34; file_xslt=\u0026#34;$SCRIPT_DIR/roles.xslt\u0026#34; file_name=\u0026#34;All roles and resource.csv\u0026#34; extension=\u0026#34;*.zip\u0026#34; file_prefix=true allow_old_files=true delete_matrix=false password=true delimit_if_spaces() { filename=\u0026#34;$@\u0026#34; if [[ \u0026#34;$@\u0026#34; =~ ( |\\\u0026#39;) ]]; then filename=\u0026#34;\\\u0026#34;$@\\\u0026#34;\u0026#34; fi echo $filename } do_help() { echo; echo \u0026#34;Script usage flags: [-s] [-m] [-i] [-x] [-o] [-f] [-b] [-a] [-d] [-q] [-p]\u0026#34; echo; echo \u0026#34;-s\u0026#34; echo \u0026#34; The directory to scan for files using the file mask\u0026#34; echo \u0026#34; Default : \u0026#34;$(delimit_if_spaces $dir_root) echo \u0026#34;-m\u0026#34; echo \u0026#34; The file mask applied when scanning for files\u0026#34; echo \u0026#34; Default : \u0026#34;$(delimit_if_spaces $extension) echo \u0026#34;-i\u0026#34; echo \u0026#34; The full path to the input file which the XSLT stylesheet runs against\u0026#34; echo \u0026#34;-x\u0026#34; echo \u0026#34; The full path of the XSLT stylesheet\u0026#34; echo \u0026#34; Default : \u0026#34;$(delimit_if_spaces $file_xslt) echo \u0026#34;-o\u0026#34; echo \u0026#34; The directory where the output file will be saved\u0026#34; echo \u0026#34; Default : \u0026#34;$(delimit_if_spaces $zip_output) echo \u0026#34;-f\u0026#34; echo \u0026#34; Suffix assigned to the output file\u0026#34; echo \u0026#34; Default : \u0026#34;$(delimit_if_spaces $file_name) echo \u0026#34;-b\u0026#34; echo \u0026#34; Prepend the current date to output file (true | false)\u0026#34; echo \u0026#34; Default : $file_prefix\u0026#34; echo \u0026#34;-a\u0026#34; echo \u0026#34; Allow processing of files created in the past (true | false); only valid with the -s option\u0026#34; echo \u0026#34; Default : $allow_old_files\u0026#34; echo \u0026#34;-d\u0026#34; echo \u0026#34; Delete input file (true | false); only valid with the -s option\u0026#34; echo \u0026#34; Default : $delete_matrix\u0026#34;; echo \u0026#34;-q\u0026#34; echo \u0026#34; Partial name of the Role or Resource (contains) to use as query\u0026#34; echo \u0026#34;-p\u0026#34; echo \u0026#34; Use a password with 7z when extracting the XML file from the input (true | false)\u0026#34; echo \u0026#34; Default : $password\u0026#34; echo; echo \u0026#34;Please note that this script requires xsltproc and 7z\u0026#34; echo; exit 0; } while getopts \u0026#34;:hi:s:m:o:x:f:b:a:d:q:p:\u0026#34; opt; do case $opt in h) do_help ;; i) matrix_file=\u0026#34;$OPTARG\u0026#34; ;; s) dir_root=\u0026#34;$OPTARG\u0026#34; ;; m) extension=\u0026#34;$OPTARG\u0026#34; ;; o) zip_output=\u0026#34;$OPTARG\u0026#34; ;; x) file_xslt=\u0026#34;$OPTARG\u0026#34; ;; f) file_name=\u0026#34;$OPTARG\u0026#34; ;; b) file_prefix=\u0026#34;$OPTARG\u0026#34; ;; a) allow_old_files=\u0026#34;$OPTARG\u0026#34; ;; d) delete_matrix=\u0026#34;$OPTARG\u0026#34; ;; q) query=\u0026#34;$OPTARG\u0026#34; ;; p) password=\u0026#34;$OPTARG\u0026#34; ;; \\?) echo \u0026#34;Invalid option\u0026#34; ;; :) echo \u0026#34;Error: Option -$OPTARG requires an argument.\u0026#34; \u0026gt;\u0026amp;2; exit 1 ;; esac done echo; if [ ! -z $matrix_file ]; then allow_old_files=true delete_matrix=false if [ $password != true ]; then password=false fi fi dir_root=$(echo $dir_root | sed \u0026#39;s:/*$::\u0026#39;)\u0026#34;/\u0026#34; zip_output=$(echo $zip_output | sed \u0026#39;s:/*$::\u0026#39;)\u0026#34;/\u0026#34; if [ -z $matrix_file ]; then zips=\u0026#34;$dir_root\u0026#34;\u0026#34;$extension\u0026#34; matrix_file=$(ls $zips | tail -n 1) fi start_time=$(date) today_date=$(date +\u0026#34;%Y.%m.%d\u0026#34;) echo \u0026#34;Allow old files : \u0026#34;$allow_old_files echo \u0026#34;Use password : \u0026#34;$password echo \u0026#34;Delete matrix : \u0026#34;$delete_matrix echo \u0026#34;Use file prefix : \u0026#34;$file_prefix echo \u0026#34;Root directory : \u0026#34;$(delimit_if_spaces $dir_root) echo \u0026#34;Output directory : \u0026#34;$(delimit_if_spaces $zip_output) echo \u0026#34;XSLT file : \u0026#34;$(delimit_if_spaces $file_xslt) echo \u0026#34;Matrix file : \u0026#34;$(delimit_if_spaces $matrix_file) if [[ \u0026#34;$query\u0026#34; =~ ^.+$ ]]; then echo \u0026#34;Role query text : \u0026#34;$(delimit_if_spaces $query); fi if [ -z \u0026#34;$matrix_file\u0026#34; ] || [ ! -f \u0026#34;$matrix_file\u0026#34; ]; then echo echo \u0026#34;Input file not found\u0026#34; echo exit 0 fi echo echo \u0026#34;Matrix zipped : \u0026#34;$(delimit_if_spaces $matrix_file) check_date=$(date +\u0026#34;%Y-%m-%d\u0026#34;) filetime=$(find $matrix_file -type f -newermt $check_date) if [ $allow_old_files = false ]; then if [[ \u0026#34;$filetime\u0026#34; =~ ^$ ]]; then echo echo \u0026#34;The file [$(basename $matrix_file)] is older than today. Not allowed!\u0026#34; echo exit 0 fi fi matrix_file_name=$(basename $matrix_file) IFS=\u0026#39;_\u0026#39; read -ra ADDR \u0026lt;\u0026lt;\u0026lt; \u0026#34;$matrix_file_name\u0026#34; begin=\u0026#34;${ADDR[0]}\u0026#34; zipcmd=(7z); zipcmd+=(x); zipcmd+=($matrix_file); zipcmd+=(-aoa); if [ $password == \u0026#39;true\u0026#39; ]; then password=\u0026#34;${begin: -4}.${ADDR[1]}.${ADDR[2]}\u0026#34; zipcmd+=(-p$password); fi echo \u0026#34;ZIP password : \u0026#34;$(delimit_if_spaces $password) zipcmd+=(-o$zip_output); zipcmd+=(*.xml); \u0026#34;${zipcmd[@]}\u0026#34; dir_extracted_files=\u0026#34;$zip_output${begin: -4}_${ADDR[1]}_${ADDR[2]}*.xml\u0026#34; file=$(ls $dir_extracted_files | tail -n 1) filesize=$(wc -c $file | awk \u0026#39;{print $1}\u0026#39;) if [ ! -f $file ] || [ $filesize = 0 ]; then echo echo \u0026#34;Something went wrong\u0026#34; echo exit 0 fi echo echo \u0026#34;Matrix XML file : \u0026#34;$(delimit_if_spaces $file) if [ $file_prefix = true ]; then output_starting=\u0026#34;${begin: -4}_${ADDR[1]}_${ADDR[2]}\u0026#34; output_ending=\u0026#34;_run_on\u0026#34;\u0026#34;_\u0026#34;$(date +\u0026#34;%Y_%m_%d_%H_%M_%S\u0026#34;)\u0026#34;\u0026#34;_\u0026#34;$file_name\u0026#34; echo \u0026#34;Output starting : \u0026#34;$(delimit_if_spaces $output_starting) echo \u0026#34;Output ending : \u0026#34;$(delimit_if_spaces $output_ending) output_file=\u0026#34;$zip_output\u0026#34;\u0026#34;$output_starting\u0026#34;\u0026#34;$output_ending\u0026#34; else output_file=\u0026#34;$zip_output\u0026#34;\u0026#34;$file_name\u0026#34; fi cmd=(xsltproc); cmd+=(--output); cmd+=(\u0026#34;$output_file\u0026#34;); if [[ \u0026#34;$query\u0026#34; =~ ^.+$ ]]; then cmd+=(--stringparam role_name \u0026#34;$query\u0026#34;); fi cmd+=($file_xslt); cmd+=($file); \u0026#34;${cmd[@]}\u0026#34; echo if [ -f \u0026#34;$output_file\u0026#34; ]; then echo \u0026#34;File written : \u0026#34;$(delimit_if_spaces $output_file) fi if [ $delete_matrix = true ] \u0026amp;\u0026amp; [ -f \u0026#34;$matrix_file\u0026#34; ]; then echo \u0026#34;Deleting file : \u0026#34;$(delimit_if_spaces $file) remove=$(rm $file) fi end_time=$(date) echo echo \u0026#34;End Date : \u0026#34;$end_time echo \u0026#34;Start Date : \u0026#34;$start_time echo exit 0 ","date":"20 March 2026","externalUrl":null,"permalink":"/posts/code/xslt/","section":"Posts","summary":"#!/bin/bash SCRIPT_DIR=$( cd -- \"$( dirname -- \"${BASH_SOURCE[0]}\" )\" \u0026\u003e /dev/null \u0026\u0026 pwd ) dir_root=$SCRIPT_DIR\"/\" zip_output=$SCRIPT_DIR\"/\" file_xslt=\"$SCRIPT_DIR/roles.xslt\" file_name=\"All roles and resource.csv\" extension=\"*.zip\" file_prefix=true allow_old_files=true delete_matrix=false password=true delimit_if_spaces() { filename=\"$@\" if [[ \"$@\" =~ ( |\\') ]]; then filename=\"\\\"$@\\\"\" fi echo $filename } do_help() { echo; echo \"Script usage flags: [-s] [-m] [-i] [-x] [-o] [-f] [-b] [-a] [-d] [-q] [-p]\" echo; echo \"-s\" echo \" The directory to scan for files using the file mask\" echo \" Default : \"$(delimit_if_spaces $dir_root) echo \"-m\" echo \" The file mask applied when scanning for files\" echo \" Default : \"$(delimit_if_spaces $extension) echo \"-i\" echo \" The full path to the input file which the XSLT stylesheet runs against\" echo \"-x\" echo \" The full path of the XSLT stylesheet\" echo \" Default : \"$(delimit_if_spaces $file_xslt) echo \"-o\" echo \" The directory where the output file will be saved\" echo \" Default : \"$(delimit_if_spaces $zip_output) echo \"-f\" echo \" Suffix assigned to the output file\" echo \" Default : \"$(delimit_if_spaces $file_name) echo \"-b\" echo \" Prepend the current date to output file (true | false)\" echo \" Default : $file_prefix\" echo \"-a\" echo \" Allow processing of files created in the past (true | false); only valid with the -s option\" echo \" Default : $allow_old_files\" echo \"-d\" echo \" Delete input file (true | false); only valid with the -s option\" echo \" Default : $delete_matrix\"; echo \"-q\" echo \" Partial name of the Role or Resource (contains) to use as query\" echo \"-p\" echo \" Use a password with 7z when extracting the XML file from the input (true | false)\" echo \" Default : $password\" echo; echo \"Please note that this script requires xsltproc and 7z\" echo; exit 0; } while getopts \":hi:s:m:o:x:f:b:a:d:q:p:\" opt; do case $opt in h) do_help ;; i) matrix_file=\"$OPTARG\" ;; s) dir_root=\"$OPTARG\" ;; m) extension=\"$OPTARG\" ;; o) zip_output=\"$OPTARG\" ;; x) file_xslt=\"$OPTARG\" ;; f) file_name=\"$OPTARG\" ;; b) file_prefix=\"$OPTARG\" ;; a) allow_old_files=\"$OPTARG\" ;; d) delete_matrix=\"$OPTARG\" ;; q) query=\"$OPTARG\" ;; p) password=\"$OPTARG\" ;; \\?) echo \"Invalid option\" ;; :) echo \"Error: Option -$OPTARG requires an argument.\" \u003e\u00262; exit 1 ;; esac done echo; if [ ! -z $matrix_file ]; then allow_old_files=true delete_matrix=false if [ $password != true ]; then password=false fi fi dir_root=$(echo $dir_root | sed 's:/*$::')\"/\" zip_output=$(echo $zip_output | sed 's:/*$::')\"/\" if [ -z $matrix_file ]; then zips=\"$dir_root\"\"$extension\" matrix_file=$(ls $zips | tail -n 1) fi start_time=$(date) today_date=$(date +\"%Y.%m.%d\") echo \"Allow old files : \"$allow_old_files echo \"Use password : \"$password echo \"Delete matrix : \"$delete_matrix echo \"Use file prefix : \"$file_prefix echo \"Root directory : \"$(delimit_if_spaces $dir_root) echo \"Output directory : \"$(delimit_if_spaces $zip_output) echo \"XSLT file : \"$(delimit_if_spaces $file_xslt) echo \"Matrix file : \"$(delimit_if_spaces $matrix_file) if [[ \"$query\" =~ ^.+$ ]]; then echo \"Role query text : \"$(delimit_if_spaces $query); fi if [ -z \"$matrix_file\" ] || [ ! -f \"$matrix_file\" ]; then echo echo \"Input file not found\" echo exit 0 fi echo echo \"Matrix zipped : \"$(delimit_if_spaces $matrix_file) check_date=$(date +\"%Y-%m-%d\") filetime=$(find $matrix_file -type f -newermt $check_date) if [ $allow_old_files = false ]; then if [[ \"$filetime\" =~ ^$ ]]; then echo echo \"The file [$(basename $matrix_file)] is older than today. Not allowed!\" echo exit 0 fi fi matrix_file_name=$(basename $matrix_file) IFS='_' read -ra ADDR \u003c\u003c\u003c \"$matrix_file_name\" begin=\"${ADDR[0]}\" zipcmd=(7z); zipcmd+=(x); zipcmd+=($matrix_file); zipcmd+=(-aoa); if [ $password == 'true' ]; then password=\"${begin: -4}.${ADDR[1]}.${ADDR[2]}\" zipcmd+=(-p$password); fi echo \"ZIP password : \"$(delimit_if_spaces $password) zipcmd+=(-o$zip_output); zipcmd+=(*.xml); \"${zipcmd[@]}\" dir_extracted_files=\"$zip_output${begin: -4}_${ADDR[1]}_${ADDR[2]}*.xml\" file=$(ls $dir_extracted_files | tail -n 1) filesize=$(wc -c $file | awk '{print $1}') if [ ! -f $file ] || [ $filesize = 0 ]; then echo echo \"Something went wrong\" echo exit 0 fi echo echo \"Matrix XML file : \"$(delimit_if_spaces $file) if [ $file_prefix = true ]; then output_starting=\"${begin: -4}_${ADDR[1]}_${ADDR[2]}\" output_ending=\"_run_on\"\"_\"$(date +\"%Y_%m_%d_%H_%M_%S\")\"\"_\"$file_name\" echo \"Output starting : \"$(delimit_if_spaces $output_starting) echo \"Output ending : \"$(delimit_if_spaces $output_ending) output_file=\"$zip_output\"\"$output_starting\"\"$output_ending\" else output_file=\"$zip_output\"\"$file_name\" fi cmd=(xsltproc); cmd+=(--output); cmd+=(\"$output_file\"); if [[ \"$query\" =~ ^.+$ ]]; then cmd+=(--stringparam role_name \"$query\"); fi cmd+=($file_xslt); cmd+=($file); \"${cmd[@]}\" echo if [ -f \"$output_file\" ]; then echo \"File written : \"$(delimit_if_spaces $output_file) fi if [ $delete_matrix = true ] \u0026\u0026 [ -f \"$matrix_file\" ]; then echo \"Deleting file : \"$(delimit_if_spaces $file) remove=$(rm $file) fi end_time=$(date) echo echo \"End Date : \"$end_time echo \"Start Date : \"$start_time echo exit 0","title":"Bash script for XSLT","type":"posts"},{"content":"","date":"20 March 2026","externalUrl":null,"permalink":"/tags/xslt/","section":"Tag","summary":"","title":"Xslt","type":"tags"},{"content":"#!/usr/bin/env bash while read line; do me=$(echo $line) if [[ \u0026#34;$me\u0026#34; =~ ^Podcast\\ Download.+ ]]; then url=$(echo \u0026#34;$me\u0026#34; | cut -d \u0026#39; \u0026#39; -f 4) type=$(echo \u0026#34;$me\u0026#34; | cut -d \u0026#39; \u0026#39; -f 6) echo \u0026#34;$type : $(date) =\u0026gt; $url\u0026#34; \u0026gt;\u0026gt; /home/karmst/.config/newsboat/podcasts.txt if [[ \u0026#34;$type\u0026#34; =~ ^video ]]; then dunstify \u0026#34;Playing Video : $url\u0026#34; mpv -fs --really-quiet --no-terminal $url else dunstify \u0026#34;Playing Audio : $url\u0026#34; mpc add $url mpc play fi fi done ","date":"5 March 2026","externalUrl":null,"permalink":"/posts/linux/newsboat/","section":"Posts","summary":"#!/usr/bin/env bash while read line; do me=$(echo $line) if [[ \"$me\" =~ ^Podcast\\ Download.+ ]]; then url=$(echo \"$me\" | cut -d ' ' -f 4) type=$(echo \"$me\" | cut -d ' ' -f 6) echo \"$type : $(date) =\u003e $url\" \u003e\u003e /home/karmst/.config/newsboat/podcasts.txt if [[ \"$type\" =~ ^video ]]; then dunstify \"Playing Video : $url\" mpv -fs --really-quiet --no-terminal $url else dunstify \"Playing Audio : $url\" mpc add $url mpc play fi fi done","title":"Newsboat streaming","type":"posts"},{"content":"This page demonstrates how to use an XSLT stylesheet to convert an XBEL (XML Bookmark Exchange Language) file into a structured, delimited text format. XBEL is a standard XML format used for storing and exchanging bookmarks between applications.\nOverview # The provided XSLT stylesheet, transform.xsl, processes an XBEL file (e.g., bookmarks.xbel) and outputs the bookmarks in a pipe-delimited text format. This format is useful for importing bookmarks into other applications or for further processing.\nHow It Works # The XSLT stylesheet recursively processes each folder and bookmark in the XBEL file, constructing the folder path and appending the bookmark title and URL.\nUse Cases # Data Migration: Convert XBEL bookmarks for use in other applications. Data Analysis: Process bookmark data for analysis or reporting. Integration: Import bookmarks into custom applications or databases. Generating the output # xsltproc \u0026ndash;output bookmarks.txt transform.xsl bookmarks.xbel\nSearch|Brave|https://search.brave.com/ Search|Ecosia|https://www.ecosia.org/ Search|Startpage|https://www.startpage.com/en/ Search|Google|https://www.google.co.uk/ Sport/Golf/Golf Clubs|10 Best Iron Sets for Beginners|https://www.golfbidder.co.uk/guides-and-advice/the-clubhouse/best-beginner-iron-sets Sport/Golf/Golf Clubs|TaylorMade M2 2019|https://www.golfbidder.co.uk/taylormade-m2-2019-iron-set Sport/Golf/Golf Clubs|Mizuno JPX 919|https://www.golfbidder.co.uk/mizuno-jpx-919-hot-metal-iron-set Sport/Golf/Golf Clubs|Used PING Drivers That Will Revolutionise Your Game.|https://www.golfclubs4cash.co.uk/collections/ping-drivers A snippet from the bookmarks.xbel file is shown below.\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;xbel version=\u0026#34;1.0\u0026#34;\u0026gt; \u0026lt;folder id=\u0026#34;1773\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Search\u0026lt;/title\u0026gt; \u0026lt;bookmark href=\u0026#34;https://search.brave.com/\u0026#34; id=\u0026#34;1775\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Brave\u0026lt;/title\u0026gt; \u0026lt;/bookmark\u0026gt; \u0026lt;bookmark href=\u0026#34;https://www.ecosia.org/\u0026#34; id=\u0026#34;1776\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Ecosia\u0026lt;/title\u0026gt; \u0026lt;/bookmark\u0026gt; \u0026lt;bookmark href=\u0026#34;https://www.startpage.com/en/\u0026#34; id=\u0026#34;1777\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Startpage\u0026lt;/title\u0026gt; \u0026lt;/bookmark\u0026gt; \u0026lt;bookmark href=\u0026#34;https://www.google.co.uk/\u0026#34; id=\u0026#34;1778\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Google\u0026lt;/title\u0026gt; \u0026lt;/bookmark\u0026gt; \u0026lt;/folder\u0026gt; \u0026lt;folder id=\u0026#34;1449\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Sport\u0026lt;/title\u0026gt; \u0026lt;folder id=\u0026#34;1658\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Golf\u0026lt;/title\u0026gt; \u0026lt;folder id=\u0026#34;1693\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Golf Clubs\u0026lt;/title\u0026gt; \u0026lt;bookmark href=\u0026#34;https://www.golfbidder.co.uk/guides-and-advice/the-clubhouse/best-beginner-iron-sets\u0026#34; id=\u0026#34;1690\u0026#34;\u0026gt; \u0026lt;title\u0026gt;10 Best Iron Sets for Beginners\u0026lt;/title\u0026gt; \u0026lt;/bookmark\u0026gt; \u0026lt;bookmark href=\u0026#34;https://www.golfbidder.co.uk/taylormade-m2-2019-iron-set\u0026#34; id=\u0026#34;1691\u0026#34;\u0026gt; \u0026lt;title\u0026gt;TaylorMade M2 2019\u0026lt;/title\u0026gt; \u0026lt;/bookmark\u0026gt; \u0026lt;bookmark href=\u0026#34;https://www.golfbidder.co.uk/mizuno-jpx-919-hot-metal-iron-set\u0026#34; id=\u0026#34;1692\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Mizuno JPX 919\u0026lt;/title\u0026gt; \u0026lt;/bookmark\u0026gt; \u0026lt;bookmark href=\u0026#34;https://www.golfclubs4cash.co.uk/collections/ping-drivers\u0026#34; id=\u0026#34;1696\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Used PING Drivers That Will Revolutionise Your Game.\u0026lt;/title\u0026gt; \u0026lt;/bookmark\u0026gt; \u0026lt;/folder\u0026gt; \u0026lt;/folder\u0026gt; \u0026lt;/folder\u0026gt; \u0026lt;/xbel\u0026gt; The transform.xsl stylesheet is shown below.\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;xsl:stylesheet exclude-result-prefixes=\u0026#34;exsl\u0026#34; version=\u0026#34;1.0\u0026#34; xmlns:exsl=\u0026#34;http://exslt.org/common\u0026#34; xmlns:xsl=\u0026#34;http://www.w3.org/1999/XSL/Transform\u0026#34;\u0026gt; \u0026lt;xsl:strip-space elements=\u0026#34;*\u0026#34;/\u0026gt; \u0026lt;xsl:preserve-space elements=\u0026#34;value component operation-data password check-password\u0026#34;/\u0026gt; \u0026lt;xsl:output indent=\u0026#34;yes\u0026#34; method=\u0026#34;xml\u0026#34;/\u0026gt; \u0026lt;xsl:template match=\u0026#34;xbel\u0026#34;\u0026gt; \u0026lt;xsl:apply-templates select=\u0026#34;folder\u0026#34; /\u0026gt; \u0026lt;/xsl:template\u0026gt; \u0026lt;xsl:template match=\u0026#34;folder\u0026#34;\u0026gt; \u0026lt;xsl:param name=\u0026#34;folderName\u0026#34;/\u0026gt; \u0026lt;xsl:variable name=\u0026#34;varFolderName\u0026#34; select=\u0026#34;./title/text()\u0026#34; /\u0026gt; \u0026lt;xsl:variable name=\u0026#34;varLevel\u0026#34;\u0026gt; \u0026lt;xsl:choose\u0026gt; \u0026lt;xsl:when test=\u0026#34;string-length($folderName) \u0026gt; 0\u0026#34;\u0026gt; \u0026lt;xsl:value-of select=\u0026#34;concat($folderName, \u0026#39;/\u0026#39;, $varFolderName)\u0026#34; /\u0026gt; \u0026lt;/xsl:when\u0026gt; \u0026lt;xsl:otherwise\u0026gt; \u0026lt;xsl:value-of select=\u0026#34;$varFolderName\u0026#34; /\u0026gt; \u0026lt;/xsl:otherwise\u0026gt; \u0026lt;/xsl:choose\u0026gt; \u0026lt;/xsl:variable\u0026gt; \u0026lt;xsl:for-each select=\u0026#34;./bookmark\u0026#34;\u0026gt; \u0026lt;xsl:variable name=\u0026#34;varURL\u0026#34; select=\u0026#34;./@href\u0026#34; /\u0026gt; \u0026lt;xsl:variable name=\u0026#34;varCurrent\u0026#34; select=\u0026#34;.\u0026#34; /\u0026gt; \u0026lt;xsl:message\u0026gt; \u0026lt;xsl:value-of select=\u0026#34;$varLevel\u0026#34; /\u0026gt; \u0026lt;xsl:value-of select=\u0026#34;\u0026#39;|\u0026#39;\u0026#34; /\u0026gt; \u0026lt;xsl:value-of select=\u0026#34;$varCurrent\u0026#34; /\u0026gt; \u0026lt;xsl:value-of select=\u0026#34;\u0026#39;|\u0026#39;\u0026#34; /\u0026gt; \u0026lt;xsl:value-of select=\u0026#34;$varURL\u0026#34; /\u0026gt; \u0026lt;/xsl:message\u0026gt; \u0026lt;/xsl:for-each\u0026gt; \u0026lt;xsl:if test=\u0026#34;folder\u0026#34;\u0026gt; \u0026lt;xsl:apply-templates select=\u0026#34;folder\u0026#34;\u0026gt; \u0026lt;xsl:with-param name=\u0026#34;folderName\u0026#34;\u0026gt;\u0026lt;xsl:value-of select=\u0026#34;$varLevel\u0026#34; /\u0026gt;\u0026lt;/xsl:with-param\u0026gt; \u0026lt;/xsl:apply-templates\u0026gt; \u0026lt;/xsl:if\u0026gt; \u0026lt;/xsl:template\u0026gt; \u0026lt;xsl:template match=\u0026#34;node()|@*\u0026#34;\u0026gt; \u0026lt;xsl:apply-templates select=\u0026#34;@*|node()\u0026#34;/\u0026gt; \u0026lt;/xsl:template\u0026gt; \u0026lt;/xsl:stylesheet\u0026gt; ","date":"3 October 2025","externalUrl":null,"permalink":"/posts/code/xslt-xbel/","section":"Posts","summary":"This page demonstrates how to use an XSLT stylesheet to convert an XBEL (XML Bookmark Exchange Language) file into a structured, delimited text format. XBEL is a standard XML format used for storing and exchanging bookmarks between applications.\n","title":"Convert XBEL file","type":"posts"},{"content":"Belkast Consulting uses several products to support the company. The order is by no means indicative of how useful we find the software; it is merely a list ordered by how often the software is used.\nTailscale # Mesh networking. Except for the main belkast.com website (Hugo) and NextCloud, no Belkast server exposes any ports to the internet. Everything is routed through the Tailscale network.\nPangolin # Authentication is required for all private Belkast services, and the request must originate in a select list of countries. Download from this link.\nHugo # Static website generator. A cron job runs every 30mins to synchronize markdown files stored in NextCloud, thus allowing for website updates from anywhere. Download from this link.\nNextCloud # File sync. Download from this link.\nGatus # Service status which supports alerts if a belkast service is down. Download from this link.\nPrivateBin # Encrypted paste service allowing file sharing in a secure manner. Data is encrypted using AES256, and can be optionally protected with a password and set to be read only once. Download from this link.\nSuiteCRM # Customer Relationship Management software. Primarily used for Belkast invoice PDF generation. Download from this link.\nSearxNG # This is a private and self hosted search engine which replaces google.com with no tracking or browser fingerprinting. Download from this link.\nforgejo # This is a self hosted Git. Download from this link.\nOttrBox # OttrBox is a self-hosted file sharing platform. Download from this link.\nsftpgo # Full-featured and highly configurable file transfer solution. Server protocols: SFTP, HTTP/S, FTP/S, WebDAV. Storage backends: local filesystem, encrypted local filesystem, S3 (compatible) Object Storage, Google Cloud Storage, Azure Blob Storage, other SFTP servers. Download from this link\nKimai # Time tracking. Download from this link.\nShlink # Link shortener. Download from this link.\nttyd # SSH access from a Browser. Download from this link.\nMiscellaneous # rsync is used to synchronize files between multiple destinations for redundant backups (S3, Google, OneDrive, NAS, etc)\n","date":"20 September 2025","externalUrl":null,"permalink":"/sw/","section":"Posts","summary":"Belkast Consulting uses several products to support the company. The order is by no means indicative of how useful we find the software; it is merely a list ordered by how often the software is used.\n","title":"Belkast Software","type":"posts"},{"content":"","date":"20 September 2025","externalUrl":null,"permalink":"/tags/company/","section":"Tag","summary":"","title":"Company","type":"tags"},{"content":"This little script lets you cycle through all your open windows on Linux. I’ve got it bound to Ctrl+Alt+Down and also hooked up to left-click on a custom Polybar module.\n#!/usr/bin/env bash DUNST_TIMEOUT=1500 mapfile -t windows \u0026lt; \u0026lt;( wmctrl -l | cut -d \u0026#39; \u0026#39; -f1 ) WINDOW_COUNT=${#windows[@]} if [ \u0026#34;$WINDOW_COUNT\u0026#34; -eq 0 ]; then exit 0 fi if [ \u0026#34;$WINDOW_COUNT\u0026#34; -eq 1 ]; then WIN_ID=\u0026#34;${windows[0]}\u0026#34; wmctrl -i -a $WIN_ID exit 0 fi CURRENT=$(xprop -root 32x \u0026#39;\\t$0\u0026#39; _NET_ACTIVE_WINDOW | cut -f 2) ME=${CURRENT/0x/0x0} for i in \u0026#34;${!windows[@]}\u0026#34;; do if [[ \u0026#34;${windows[$i]}\u0026#34; = \u0026#34;${ME}\u0026#34; ]]; then INDEX=\u0026#34;${i}\u0026#34;; ((INDEX++)) fi done ((WINDOW_COUNT--)) if [ \u0026#34;$INDEX\u0026#34; -gt \u0026#34;$WINDOW_COUNT\u0026#34; ]; then INDEX=0 fi WIN_ID=\u0026#34;${windows[INDEX]}\u0026#34; WIN_NAME=$(xprop -id $WIN_ID | awk \u0026#39;/_NET_WM_NAME/{$1=$2=\u0026#34;\u0026#34;;print}\u0026#39; | cut -d\u0026#39;\u0026#34;\u0026#39; -f2) dunstify \u0026#34;$WIN_NAME\u0026#34; --timeout=$DUNST_TIMEOUT wmctrl -i -a $WIN_ID ","date":"14 May 2025","externalUrl":null,"permalink":"/i3-alt-tab/","section":"Posts","summary":"This little script lets you cycle through all your open windows on Linux. I’ve got it bound to Ctrl+Alt+Down and also hooked up to left-click on a custom Polybar module.\n","title":"Alt-Tab script for i3","type":"posts"},{"content":"This script uses pyradio, rofi, and the kitty terminal emulator. The pyradio stations.csv file is read into an array The array is then cleaned with an awk command and a sed command The cleaned array is then piped into rofi When a rofi choice is made, a grep for the selected item is done Finally, the grepped line number is sent to pyradio using the -p command line flag The format of the pyradio stations.csv file is shown below. BBC Radio Ulster,http://a.files.bbci.co.uk/ms6/live/3441A116-B12E-4D2F-ACA8-C1984642FA4B/audio/simulcast/hls/uk/pc_hd_abr_v2/ak/bbc_radio_ulster.m3u8,, LBC London,http://media-ice.musicradio.com:80/LBCLondon,,\n#!/usr/bin/env bash BROWSER=pyradio MYFILE=\u0026#34;$HOME/.config/pyradio/stations.csv\u0026#34; stations=$(grep -n \u0026#34;\u0026#34; $MYFILE) mylist=$(printf \u0026#39;%s\\n\u0026#39; \u0026#34;${stations[@]}\u0026#34; | awk -F\u0026#34;:\u0026#34; \u0026#39;{print $2}\u0026#39; | sed -E \u0026#39;s/^(.+),.+/\\1/\u0026#39;) choice=$(printf \u0026#39;%s\\n\u0026#39; \u0026#34;$mylist\u0026#34; | rofi -dmenu -i -l 25 -p \u0026#34;Station Picker ($BROWSER)\u0026#34;) \u0026#34;$@\u0026#34; || exit if [ \u0026#34;$choice\u0026#34; ]; then LINES=$(grep -n \u0026#34;$choice\u0026#34; $MYFILE) ME=$(echo $LINES | cut -d : -f 1) RUN=$(kitty --class my_term pyradio -p $ME) fi ","date":"26 April 2025","externalUrl":null,"permalink":"/posts/linux/radio/","section":"Posts","summary":"This script uses pyradio, rofi, and the kitty terminal emulator. The pyradio stations.csv file is read into an array The array is then cleaned with an awk command and a sed command The cleaned array is then piped into rofi When a rofi choice is made, a grep for the selected item is done Finally, the grepped line number is sent to pyradio using the -p command line flag The format of the pyradio stations.csv file is shown below. BBC Radio Ulster,http://a.files.bbci.co.uk/ms6/live/3441A116-B12E-4D2F-ACA8-C1984642FA4B/audio/simulcast/hls/uk/pc_hd_abr_v2/ak/bbc_radio_ulster.m3u8,, LBC London,http://media-ice.musicradio.com:80/LBCLondon,,\n","title":"Rofi radio station picker","type":"posts"},{"content":"#!/usr/bin/env bash COLOR=$(xcolor) MY_NEW_VAR=$(echo $COLOR | sed -e \u0026#39;s/\\r//g\u0026#39;) echo \u0026#34;$MY_NEW_VAR\u0026#34; | xclip -se c ","date":"8 April 2025","externalUrl":null,"permalink":"/posts/linux/xcolor/","section":"Posts","summary":"#!/usr/bin/env bash COLOR=$(xcolor) MY_NEW_VAR=$(echo $COLOR | sed -e 's/\\r//g') echo \"$MY_NEW_VAR\" | xclip -se c","title":"Copy selected colour to clipboard","type":"posts"},{"content":"","date":"27 March 2025","externalUrl":null,"permalink":"/tags/android/","section":"Tag","summary":"","title":"Android","type":"tags"},{"content":" Go to Settings. Tap on About Microsoft Edge. Tap on Privacy and Terms. At the bottom of the screen, tap on the Edge build number (e.g., Edge Canary 136.0.3220.0) 5 times to enable Developer Options. In the Developer Options, tap on Extension install by id. To find the extension ID, follow these steps: Open the Microsoft Edge Web Store. Find and select the extension you want to install. Look at the URL in the address bar. The extension ID is the long string of characters at the end of the URL (e.g. plkbiaiofflbcpfahbgmfhfmdchigfcb for Google as New Tab). Enter the extension ID into the Extension install by id field on your Edge Canary browser. The extension should now be installed in the Edge Canary browser. ","date":"27 March 2025","externalUrl":null,"permalink":"/android-edge-extension/","section":"Posts","summary":" Go to Settings. Tap on About Microsoft Edge. Tap on Privacy and Terms. At the bottom of the screen, tap on the Edge build number (e.g., Edge Canary 136.0.3220.0) 5 times to enable Developer Options. In the Developer Options, tap on Extension install by id. To find the extension ID, follow these steps: Open the Microsoft Edge Web Store. Find and select the extension you want to install. Look at the URL in the address bar. The extension ID is the long string of characters at the end of the URL (e.g. plkbiaiofflbcpfahbgmfhfmdchigfcb for Google as New Tab). Enter the extension ID into the Extension install by id field on your Edge Canary browser. The extension should now be installed in the Edge Canary browser. ","title":"Extensions on Edge Canary","type":"posts"},{"content":"The diffDate() function takes a start date and an end date and returns the human readable form. For example : 41 mins, 53 secs\nvar varStartDate = new Date(); // Do some things here var varEndDate = new Date(); var varTotalTime = diffDate(varStartDate, varEndDate); print (varTotalTime); function diffDate (startDate, endDate) { var varRegex_Time = new RegExp(\u0026#34;^(.+?)\\\\.(.+)$\u0026#34;); var varRegex_Truncate = new RegExp(\u0026#34;^\\\\, (.+)$\u0026#34;); varStart = (startDate != undefined) ? startDate.getTime() : 0; varEnd = (endDate != undefined) ? endDate.getTime() : 0; diff = varEnd - varStart; secs = Math.floor(diff / 1000 % 60); mins = Math.floor(diff / (1000 * 60) % 60); hours = Math.floor(diff / (1000 * 60 * 60) % 24); days = Math.floor(diff / 86400000); secsText = (secs \u0026gt; 1) ? \u0026#34;, \u0026#34; + String(secs).replace(varRegex_Time, \u0026#34;$1\u0026#34;) + \u0026#34; secs\u0026#34; : \u0026#34;\u0026#34;; minsText = (mins \u0026gt; 1) ? \u0026#34;, \u0026#34; + String(mins).replace(varRegex_Time, \u0026#34;$1\u0026#34;) + \u0026#34; mins\u0026#34; : \u0026#34;\u0026#34;; hoursText = (hours \u0026gt; 1) ? \u0026#34;, \u0026#34; + String(hours).replace(varRegex_Time, \u0026#34;$1\u0026#34;) + \u0026#34; hours\u0026#34; : \u0026#34;\u0026#34;; daysText = (days \u0026gt; 1) ? String(days).replace(varRegex_Time, \u0026#34;$1\u0026#34;) + \u0026#34; days\u0026#34; : \u0026#34;\u0026#34;; return String(daysText + hoursText + minsText + secsText).replace(varRegex_Truncate, \u0026#34;$1\u0026#34;); } ","date":"25 February 2025","externalUrl":null,"permalink":"/posts/code/diffdate/","section":"Posts","summary":"The diffDate() function takes a start date and an end date and returns the human readable form. For example : 41 mins, 53 secs\nvar varStartDate = new Date(); // Do some things here var varEndDate = new Date(); var varTotalTime = diffDate(varStartDate, varEndDate); print (varTotalTime); function diffDate (startDate, endDate) { var varRegex_Time = new RegExp(\"^(.+?)\\\\.(.+)$\"); var varRegex_Truncate = new RegExp(\"^\\\\, (.+)$\"); varStart = (startDate != undefined) ? startDate.getTime() : 0; varEnd = (endDate != undefined) ? endDate.getTime() : 0; diff = varEnd - varStart; secs = Math.floor(diff / 1000 % 60); mins = Math.floor(diff / (1000 * 60) % 60); hours = Math.floor(diff / (1000 * 60 * 60) % 24); days = Math.floor(diff / 86400000); secsText = (secs \u003e 1) ? \", \" + String(secs).replace(varRegex_Time, \"$1\") + \" secs\" : \"\"; minsText = (mins \u003e 1) ? \", \" + String(mins).replace(varRegex_Time, \"$1\") + \" mins\" : \"\"; hoursText = (hours \u003e 1) ? \", \" + String(hours).replace(varRegex_Time, \"$1\") + \" hours\" : \"\"; daysText = (days \u003e 1) ? String(days).replace(varRegex_Time, \"$1\") + \" days\" : \"\"; return String(daysText + hoursText + minsText + secsText).replace(varRegex_Truncate, \"$1\"); }","title":"Difference between two Dates","type":"posts"},{"content":"","date":"25 February 2025","externalUrl":null,"permalink":"/tags/javascript/","section":"Tag","summary":"","title":"Javascript","type":"tags"},{"content":"rofi-blezz is a configurable plugin for rofi, and to use it you simply require two additional files to be copied into the rofi plugin directory:\nblezz.la blezz.so This is a great rofi extension that we use every day, but an update (at least on Void Linux) to rofi at the end of 2024 sadly broke the functionality. The good news is that we were able to recompile the blezz.c source code. The two new files are included in the blezz.zip file, which you are more than welcome to download and enjoy. ","date":"1 January 2025","externalUrl":null,"permalink":"/posts/linux/blezz/","section":"Posts","summary":"rofi-blezz is a configurable plugin for rofi, and to use it you simply require two additional files to be copied into the rofi plugin directory:\nblezz.la blezz.so This is a great rofi extension that we use every day, but an update (at least on Void Linux) to rofi at the end of 2024 sadly broke the functionality. The good news is that we were able to recompile the blezz.c source code. The two new files are included in the blezz.zip file, which you are more than welcome to download and enjoy.","title":"Blezz rofi extension","type":"posts"},{"content":"","date":"18 December 2024","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"18 December 2024","externalUrl":null,"permalink":"/categories/custom-solutions/","section":"Categories","summary":"","title":"Custom Solutions","type":"categories"},{"content":"","date":"18 December 2024","externalUrl":null,"permalink":"/tags/java/","section":"Tag","summary":"","title":"Java","type":"tags"},{"content":"We are delighted to announce that our custom development offering, soapClient.jar, has been uploaded to the Belkast GIT repository. You can download the JAR file from the releases page. We use IntelliJ IDEA Community Edition for our Java development. We have previously used Eclipse, but we now find that IntelliJ is far superior in almost every way. We also use the Azul LTS JDK for compiling the program, as the JDK is supported until 2029.\nWhat is soapClient? # It ia a JAVA program, soapClient.jar, that allows one to send data to a SOAP service using an XML template file and an input CSV file. The program reads a configuration file which defines different aspects of the program including:\nSOAP Service URL SOAP Service Username SOAP Service Password Does SOAP Service require authentication Whether to use SSL Java Keystore Java Keystore Password XML template File CSV input File Are empty CSV values allowed Username format for authentication Command line parameters # \u0026ndash;props # This is the location of the properties file to be processed by the program. If not specified, props.conf will be used.\n\u0026ndash;key # This is the key (must be 16 characters) which is used to encrypt the password that is stored in the configuration file.\n\u0026ndash;encrypt # This is the value to encrypt using the encryption key. You need to pass both \u0026ndash;key and \u0026ndash;encrypt in order to get a correct result.\n\u0026ndash;debug # This parameter takes no value and, if present, displays debug information on the screen. The debug information is always written to the debug.log file.\nHelper scripts # linux_verify.sh # The bash script shown below can be used to verify the contents of the CSV input file.\n#!/bin/bash JAVA=/usr/local/bin/java $JAVA -cp lib/commons-codec-1.17.1.jar:lib/commons-csv-1.12.0.jar:lib/commons-io-2.18.0.jar:lib/jcommander-1.82.jar:lib/soapClient.jar com.belkast.soap.userVerify linux_runner.sh # The bash script shown below can be used to run the program.\n#!/bin/bash JAVA=/usr/local/bin/java $JAVA -cp lib/commons-codec-1.17.1.jar:lib/commons-csv-1.12.0.jar:lib/commons-io-2.18.0.jar:lib/jcommander-1.82.jar:lib/soapClient.jar com.belkast.soap.webService \u0026#34;$@\u0026#34; Program components # Do not forget to include the following four JAR files as dependencies when building the soapClient.jar.\ncommons-csv-1.12.0.jar jcommander-1.82.jar commons-codec-1.17.1.jar commons-io-2.18.0.jar Configuration file # This is the main file containing the settings used by the program, and one with settings similar to those shown below is all you need to get started.\nSHIM_URL = https://test.mycompany.com:8443/IDMProv/role/service USERNAME = cn=keitha,ou=active,ou=users,o=belkast PASSWORD = PT9TKHwFgJCxATJtAAMtMwtIF0UjFal6fo5riBN+ExY= AUTH_REQUIRED = true SSL = true JAVA_KS_LOCATION = ldap.keystore JAVA_KS_PASSWORD = changeit XML_FILE = USER_TO_ROLE.xml CSV_FILE = msalah.csv CSV_ALLOW_EMPTY_COLUMN_VALUES = true USERNAME_FORMAT = (cn=.+?),ou=Active,ou=Users,o=Belkast Default Values # The settings listed below are not required to be included in the configuration file. If not present, they will be assigned the default values shown below.\nAUTH_REQUIRED : true SSL : true CSV_ALLOW_EMPTY_COLUMN_VALUES : false USERNAME_FORMAT : ^.+$ Username and Password # If the configuration file specifies that authentication to the SOAP Service is required, and either the USERNAME or PASSWORD is not specified, you will be asked to enter the missing values.\nPlease enter a username [ (cn=.+?),ou=Active,ou=Users,o=Belkast ] : Please enter your password : XML template file # This is the XML file that is sent to the SOAP service once all search and replace tokens have been processed.\n\u0026lt;soapenv:Envelope xmlns:soapenv=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34; xmlns:ser=\u0026#34;http://www.novell.com/role/service\u0026#34;\u0026gt; \u0026lt;soapenv:Header/\u0026gt; \u0026lt;soapenv:Body\u0026gt; \u0026lt;ser:requestRolesAssignmentRequest\u0026gt; \u0026lt;!--Optional:--\u0026gt; \u0026lt;ser:assignRequest\u0026gt; \u0026lt;ser:actionType\u0026gt;grant\u0026lt;/ser:actionType\u0026gt; \u0026lt;ser:assignmentType\u0026gt;USER_TO_ROLE\u0026lt;/ser:assignmentType\u0026gt; \u0026lt;ser:identity\u0026gt;USER_DN\u0026lt;/ser:identity\u0026gt; \u0026lt;ser:reason\u0026gt;DESC\u0026lt;/ser:reason\u0026gt; \u0026lt;ser:roles\u0026gt; \u0026lt;!--Zero or more repetitions:--\u0026gt; \u0026lt;ser:dnstring\u0026gt; \u0026lt;ser:dn\u0026gt;ROLE_DN\u0026lt;/ser:dn\u0026gt; \u0026lt;/ser:dnstring\u0026gt; \u0026lt;/ser:roles\u0026gt; \u0026lt;/ser:assignRequest\u0026gt; \u0026lt;/ser:requestRolesAssignmentRequest\u0026gt; \u0026lt;/soapenv:Body\u0026gt; \u0026lt;/soapenv:Envelope\u0026gt; Java keystore # If the SOAP service uses https you have one of two options when choosing which keystore to use:\nUse a local keystore, which must be specified in the configuration file and must contain all necessary certificates Use the systemwide JRE or JDK keystore. Once again, this must contain all necessary certificates Encrypt a password # To encrypt a password value, run the linux_runner.sh bash script as shown below.\n./linux_runner.sh --key 420CondoCondo420 --encrypt Password123 Clear Text Password : Password123 Encryption Key : 420CondoCondo420 Encrypted / Encoded : PT9TKHwFgJCxATJtAAMtMwtIF0UjFal6fo5riBN+ExY= Decoded / Decrypted : Password123 Verification of the CSV input file # To verify the CSV input file, run the linux_verify.sh bash script as shown below.\n./linux_verify.sh Assume we want to process the CSV input file, msalah.csv, shown below.\nUSER_DN,ROLE_DN,DESC \u0026#34;cn=msalah,ou=admins,o=belkast\u0026#34;,\u0026#34;cn=TestRole,o=belkast\u0026#34;,\u0026#34;Test Load\u0026#34; Running the linux_verify.sh bash script would result in the following output.\n./linux_verify.sh Please enter the name of the CSV file to validate : msalah.csv Invalidate a line if there are empty column values? (Y/n) : Y CSV input file : msalah.csv CSV block on empty : true CSV header tokens : USER_DN,ROLE_DN,DESC ## 2 [passed] CSV lines read : 1 CSV lines passed : 1 CSV lines failed : 0 record 1 key : USER_DN record 1 val : cn=msalah,ou=admins,o=belkast record 1 key : ROLE_DN record 1 val : cn=TestRole,o=belkast record 1 key : DESC record 1 val : Test Load CSV file records : 1 CSV file is valid : true If we had a second line with a missing DESC value, the program would return an error as shown below.\n... USER_DN,ROLE_DN,DESC \u0026#34;cn=msalah,ou=admins,o=belkast\u0026#34;,\u0026#34;cn=TestRole,o=belkast\u0026#34;,\u0026#34;Test Load 1\u0026#34; \u0026#34;cn=msalah,ou=admins,o=belkast\u0026#34;,\u0026#34;cn=TestRole,o=belkast\u0026#34;,\u0026#34;\u0026#34; ## line 2 [passed] !! line 3 [failed] : the DESC token value is empty !! line 3 [failed] : \u0026#34;cn=msalah,ou=admins,o=belkast\u0026#34;,\u0026#34;cn=TestRole,o=belkast\u0026#34;,\u0026#34;\u0026#34; !! line 3 [failed] : {DESC=, ROLE_DN=cn=TestRole,o=belkast, USER_DN=cn=msalah,ou=admins,o=belkast} CSV lines read : 2 CSV lines passed : 1 CSV lines failed : 1 CSV file records : 0 CSV file is valid : false Example usage # To run the program, just run the linux_runner.sh bash script shown at the beginning of this README.\nGetting Help # If you run the linux_runner.sh bash script with no command line parameters, you will receive a help screen as shown below.\n./linux_runner.sh Usage: \u0026lt;main class\u0026gt; [options] Options: --debug Display debug information on the screen (no value required) Default: false --encrypt Value to encrypt using the encryption key --key Encryption key (must be 16 characters) --props Location of the properties file With command line parameters # Please note that if the \u0026ndash;key command line parameter and the \u0026ndash;encrypt command line parameter are specified, the program will encrypt the passed value and exit.\nThe program checks for the following potential issues before sending the SOAP XML to the SOAP service:\nThe --key command line parameter is specified: If specified, it must have a length of 16 characters If NOT specified, you will be asked to enter it The --props command line parameter is specified: If specified, there is a check to make sure that the file exists If configured, check that the encrypted password can be decrypted The CSV input file must exist The XML template file must exist A simple example is shown below.\n./linux_runner.sh --debug true --key 420CondoCondo420 --props props_USER_TO_ROLE.conf props_USER_TO_ROLE.conf =\u0026gt; SOAP URL : https://test.mycompany.com:8443/IDMProv/role/service props_USER_TO_ROLE.conf =\u0026gt; Authentication required : true props_USER_TO_ROLE.conf =\u0026gt; Use SSL : true props_USER_TO_ROLE.conf =\u0026gt; JAVA keystore : ldap.keystore props_USER_TO_ROLE.conf =\u0026gt; JAVA keystore password : changeit props_USER_TO_ROLE.conf =\u0026gt; XML File : USER_TO_ROLE.xml props_USER_TO_ROLE.conf =\u0026gt; CSV File : msalah.csv props_USER_TO_ROLE.conf =\u0026gt; CSV allow empty : false props_USER_TO_ROLE.conf =\u0026gt; Username : cn=keitha,ou=active,ou=users,o=belkast msalah.csv : CSV file is valid : true msalah.csv : records to process : 1 Processing record 1 Record 1 : [cn=msalah,ou=admins,o=belkast, cn=TestRole,o=belkast, Test Load] Record 1 : USER_DN =\u0026gt; cn=msalah,ou=admins,o=belkast Record 1 : ROLE_DN =\u0026gt; cn=TestRole,o=belkast Record 1 : DESC =\u0026gt; Test Load For each of the lines in the input CSV file, the corresponding XML data is sent to the SOAP service. The XML data shown below is the data that is sent for our particular example. Notice that the tokens have been replaced with the data from the corresponding line in the CSV input file.\n\u0026lt;soapenv:Envelope xmlns:soapenv=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34; xmlns:ser=\u0026#34;http://www.novell.com/role/service\u0026#34;\u0026gt; \u0026lt;soapenv:Header/\u0026gt; \u0026lt;soapenv:Body\u0026gt; \u0026lt;ser:requestRolesAssignmentRequest\u0026gt; \u0026lt;!--Optional:--\u0026gt; \u0026lt;ser:assignRequest\u0026gt; \u0026lt;ser:actionType\u0026gt;grant\u0026lt;/ser:actionType\u0026gt; \u0026lt;ser:assignmentType\u0026gt;USER_TO_ROLE\u0026lt;/ser:assignmentType\u0026gt; \u0026lt;ser:identity\u0026gt;cn=msalah,ou=admins,o=belkast\u0026lt;/ser:identity\u0026gt; \u0026lt;ser:reason\u0026gt;Test Load\u0026lt;/ser:reason\u0026gt; \u0026lt;ser:roles\u0026gt; \u0026lt;!--Zero or more repetitions:--\u0026gt; \u0026lt;ser:dnstring\u0026gt; \u0026lt;ser:dn\u0026gt;cn=TestRole,o=belkast\u0026lt;/ser:dn\u0026gt; \u0026lt;/ser:dnstring\u0026gt; \u0026lt;/ser:roles\u0026gt; \u0026lt;/ser:assignRequest\u0026gt; \u0026lt;/ser:requestRolesAssignmentRequest\u0026gt; \u0026lt;/soapenv:Body\u0026gt;\u0026lt;/soapenv:Envelope\u0026gt; ","date":"18 December 2024","externalUrl":null,"permalink":"/posts/custom/soap-client/","section":"Posts","summary":"We are delighted to announce that our custom development offering, soapClient.jar, has been uploaded to the Belkast GIT repository. You can download the JAR file from the releases page. We use IntelliJ IDEA Community Edition for our Java development. We have previously used Eclipse, but we now find that IntelliJ is far superior in almost every way. We also use the Azul LTS JDK for compiling the program, as the JDK is supported until 2029.\n","title":"SOAP Client","type":"posts"},{"content":"#!/bin/bash if [[ $# != 4 ]]; then echo echo \u0026#34;Arguments passed was $# (4 required)\u0026#34; echo \u0026#34;1st argument : properties file\u0026#34; echo \u0026#34;2nd argument : encryption key\u0026#34; echo \u0026#34;3rd argument : CSV file to process\u0026#34; echo \u0026#34;4th argument : CSV column delimiter\u0026#34; echo exit fi SOAP_JAR=\u0026#34;callSoap.jar\u0026#34; SOAP_KS=\u0026#34;ldap.keystore\u0026#34; if [ ! -f $SOAP_JAR ]; then echo \u0026#34;The $SOAP_JAR JAR file does not exist\u0026#34; exit fi if [ ! -f $1 ]; then echo \u0026#34;The $1 file does not exist\u0026#34; exit fi PROG_KS=$(cat $1 | grep \u0026#34;^SSL\\\\s*=\u0026#34; | awk -F= \u0026#39;{print $NF}\u0026#39;) PROG_KS=$(echo \u0026#34;${PROG_KS#[[:space:]]}\u0026#34;) PROG_KS=$(echo \u0026#34;${PROG_KS%[[:space:]]}\u0026#34;) PROG_KS=$(echo $PROG_KS | tr \u0026#39;[:upper:]\u0026#39; \u0026#39;[:lower:]\u0026#39;) if [[ \u0026#34;$PROG_KS\u0026#34; = true ]] \u0026amp;\u0026amp; [[ ! -f $SOAP_KS ]]; then echo \u0026#34;SSL is defined as $PROG_KS in $1 : $SOAP_KS file does not exist\u0026#34; exit fi if ! [[ $2 =~ ^[0-9a-zA-Z]{16}$ ]]; then echo \u0026#34;The encryption key must have a length of 16\u0026#34; exit fi if [ ! -f $3 ]; then echo \u0026#34;The $3 file does not exist\u0026#34; exit fi PROG_DELIM=$(cat $1 | grep ARGUMENT_DELIMITER | awk -F= \u0026#39;{print $NF}\u0026#39;) PROG_DELIM=$(echo \u0026#34;${PROG_DELIM#[[:space:]]}\u0026#34;) PROG_DELIM=$(echo \u0026#34;${PROG_DELIM%[[:space:]]}\u0026#34;) if [ $PROG_DELIM = $4 ]; then echo \u0026#34;The $4 delimiter is invalid (also specified in the $1 file)\u0026#34; exit fi JRE=\u0026#34;java\u0026#34; echo -n \u0026#34;Where is java? Press enter to use \\\u0026#34;$JRE\\\u0026#34;, \\\u0026#34;v\\\u0026#34; to merely verify the CSV file, or enter a Java location: \u0026#34; read userInput if [[ -n \u0026#34;$userInput\u0026#34; ]]; then JRE=$userInput fi PROPS=$1 KEY=$2 FILE=$3 DELIMITER=$4 echo echo \u0026#34;Properties file : $PROPS\u0026#34; echo \u0026#34;Encryption key : $KEY\u0026#34; echo \u0026#34;CSV file to process : $FILE\u0026#34; echo \u0026#34;CSV column delimiter : $DELIMITER\u0026#34; echo \u0026#34;Java binary to use : $JRE\u0026#34; echo read -r firstline \u0026lt; $FILE tags=(${firstline//,/ }) LENGTH_TAGS=${#tags[@]} echo \u0026#34;Replace tokens [ \u0026#34;${#tags[@]}\u0026#34; ] : \u0026#34;${tags[@]} lines_error=() lines_total=1 lines_read=0 lines_valid=0 lines_end_check=\u0026#34;^.+\u0026#34;$DELIMITER\u0026#34;$\u0026#34; soap_sent=0 while read LINE || [ -n \u0026#34;$LINE\u0026#34; ]; do LINE=$(echo \u0026#34;${LINE#[[:space:]]}\u0026#34;) LINE=$(echo \u0026#34;${LINE%[[:space:]]}\u0026#34;) if [[ $LINE =~ ^.+$ ]]; then valid=true pos=0 IFS=$DELIMITER read -r -a valueArray \u0026lt;\u0026lt;\u0026lt; \u0026#34;$LINE\u0026#34; LENGTH_LINE=${#valueArray[@]} if [[ $LINE =~ $lines_end_check ]]; then LENGTH_LINE=$((LENGTH_LINE+1)) fi for element in \u0026#34;${tags[@]}\u0026#34;; do value=${valueArray[$pos]} value=$(sed \u0026#39;s/^[[:space:]]*//\u0026#39; \u0026lt;\u0026lt;\u0026lt; \u0026#34;$value\u0026#34;) value=$(echo \u0026#34;${value}\u0026#34; | sed -e \u0026#39;s/\\ *$//g\u0026#39;) if [[ $value =~ ^$ ]] || [[ $LENGTH_LINE != $LENGTH_TAGS ]]; then valid=false fi pos=$((pos+1)) done if [ \u0026#34;$valid\u0026#34; = true ]; then echo \u0026#34;[ PASS : line \u0026#34;$((lines_total+1))\u0026#34; ]\u0026#34; lines_valid=$((lines_valid+1)) else echo \u0026#34;[ FAIL : line \u0026#34;$((lines_total+1))\u0026#34; ]\u0026#34; this_error=$(echo \u0026#34;[ line \u0026#34;$((lines_total+1))\u0026#34; ] : $LINE\u0026#34;) lines_error+=(\u0026#34;$this_error\u0026#34;) fi lines_read=$((lines_read+1)) fi lines_total=$((lines_total+1)) done \u0026lt; \u0026lt;(tail -n +2 $FILE) if [[ ${#lines_error[@]} -gt 0 ]]; then echo echo \u0026#34;## NUMBER OF VERIFICATION ERRORS WAS \u0026#34;$((lines_read-lines_valid))\u0026#34; ##\u0026#34; printf \u0026#39;%s\\n\u0026#39; \u0026#34;${lines_error[@]}\u0026#34; fi echo echo \u0026#34;Verification result : verified lines was \u0026#34;$lines_valid\u0026#34; of \u0026#34;$lines_read if [[ $JRE = \u0026#34;v\u0026#34; || $lines_valid != $lines_read ]]; then echo exit fi while read LINE || [ -n \u0026#34;$LINE\u0026#34; ]; do LINE=$(echo \u0026#34;${LINE#[[:space:]]}\u0026#34;) LINE=$(echo \u0026#34;${LINE%[[:space:]]}\u0026#34;) if [[ $LINE =~ ^.+$ ]]; then pos=0 param=\u0026#34;\u0026#34; IFS=$DELIMITER read -r -a valueArray \u0026lt;\u0026lt;\u0026lt; \u0026#34;$LINE\u0026#34; for element in \u0026#34;${tags[@]}\u0026#34;; do value=${valueArray[$pos]} value=$(sed \u0026#39;s/^[[:space:]]*//\u0026#39; \u0026lt;\u0026lt;\u0026lt; \u0026#34;$value\u0026#34;) value=$(echo \u0026#34;${value}\u0026#34; | sed -e \u0026#39;s/\\ *$//g\u0026#39;) param=$param\u0026#34;\\\u0026#34;$element$PROG_DELIM$value\u0026#34;\\\u0026#34;\u0026#34; \u0026#34; pos=$((pos+1)) done cmd=($JRE -Djavax.net.ssl.trustStore=$SOAP_KS -jar $SOAP_JAR --key $KEY --props $PROPS --replace $param) result=$(eval ${cmd[@]}) result=$(echo $result | tr -d \u0026#39;\\n\u0026#39;) verify=$(echo $result | sed -E \u0026#39;s/^(.+)(Exception|SOAP Document received: )(.+)$/\\2\\3/\u0026#39;) if [[ ! \u0026#34;$result\u0026#34; == \u0026#34;$verify\u0026#34; ]] \u0026amp;\u0026amp; [[ ! $verify =~ \u0026#34;HTTP\u0026#34; ]] \u0026amp;\u0026amp; [[ ! $verify =~ \u0026#34;Exception\u0026#34; ]]; then soap_sent=$((soap_sent+1)) fi fi done \u0026lt; \u0026lt;(tail -n +2 $FILE) echo echo \u0026#34;Finished processing [ \u0026#34;$FILE\u0026#34; ] : lines processed was \u0026#34;$lines_read echo \u0026#34;Finished processing [ \u0026#34;$FILE\u0026#34; ] : successful calls was \u0026#34;$soap_sent ","date":"17 December 2024","externalUrl":null,"permalink":"/posts/linux/csv/","section":"Posts","summary":"#!/bin/bash if [[ $# != 4 ]]; then echo echo \"Arguments passed was $# (4 required)\" echo \"1st argument : properties file\" echo \"2nd argument : encryption key\" echo \"3rd argument : CSV file to process\" echo \"4th argument : CSV column delimiter\" echo exit fi SOAP_JAR=\"callSoap.jar\" SOAP_KS=\"ldap.keystore\" if [ ! -f $SOAP_JAR ]; then echo \"The $SOAP_JAR JAR file does not exist\" exit fi if [ ! -f $1 ]; then echo \"The $1 file does not exist\" exit fi PROG_KS=$(cat $1 | grep \"^SSL\\\\s*=\" | awk -F= '{print $NF}') PROG_KS=$(echo \"${PROG_KS#[[:space:]]}\") PROG_KS=$(echo \"${PROG_KS%[[:space:]]}\") PROG_KS=$(echo $PROG_KS | tr '[:upper:]' '[:lower:]') if [[ \"$PROG_KS\" = true ]] \u0026\u0026 [[ ! -f $SOAP_KS ]]; then echo \"SSL is defined as $PROG_KS in $1 : $SOAP_KS file does not exist\" exit fi if ! [[ $2 =~ ^[0-9a-zA-Z]{16}$ ]]; then echo \"The encryption key must have a length of 16\" exit fi if [ ! -f $3 ]; then echo \"The $3 file does not exist\" exit fi PROG_DELIM=$(cat $1 | grep ARGUMENT_DELIMITER | awk -F= '{print $NF}') PROG_DELIM=$(echo \"${PROG_DELIM#[[:space:]]}\") PROG_DELIM=$(echo \"${PROG_DELIM%[[:space:]]}\") if [ $PROG_DELIM = $4 ]; then echo \"The $4 delimiter is invalid (also specified in the $1 file)\" exit fi JRE=\"java\" echo -n \"Where is java? Press enter to use \\\"$JRE\\\", \\\"v\\\" to merely verify the CSV file, or enter a Java location: \" read userInput if [[ -n \"$userInput\" ]]; then JRE=$userInput fi PROPS=$1 KEY=$2 FILE=$3 DELIMITER=$4 echo echo \"Properties file : $PROPS\" echo \"Encryption key : $KEY\" echo \"CSV file to process : $FILE\" echo \"CSV column delimiter : $DELIMITER\" echo \"Java binary to use : $JRE\" echo read -r firstline \u003c $FILE tags=(${firstline//,/ }) LENGTH_TAGS=${#tags[@]} echo \"Replace tokens [ \"${#tags[@]}\" ] : \"${tags[@]} lines_error=() lines_total=1 lines_read=0 lines_valid=0 lines_end_check=\"^.+\"$DELIMITER\"$\" soap_sent=0 while read LINE || [ -n \"$LINE\" ]; do LINE=$(echo \"${LINE#[[:space:]]}\") LINE=$(echo \"${LINE%[[:space:]]}\") if [[ $LINE =~ ^.+$ ]]; then valid=true pos=0 IFS=$DELIMITER read -r -a valueArray \u003c\u003c\u003c \"$LINE\" LENGTH_LINE=${#valueArray[@]} if [[ $LINE =~ $lines_end_check ]]; then LENGTH_LINE=$((LENGTH_LINE+1)) fi for element in \"${tags[@]}\"; do value=${valueArray[$pos]} value=$(sed 's/^[[:space:]]*//' \u003c\u003c\u003c \"$value\") value=$(echo \"${value}\" | sed -e 's/\\ *$//g') if [[ $value =~ ^$ ]] || [[ $LENGTH_LINE != $LENGTH_TAGS ]]; then valid=false fi pos=$((pos+1)) done if [ \"$valid\" = true ]; then echo \"[ PASS : line \"$((lines_total+1))\" ]\" lines_valid=$((lines_valid+1)) else echo \"[ FAIL : line \"$((lines_total+1))\" ]\" this_error=$(echo \"[ line \"$((lines_total+1))\" ] : $LINE\") lines_error+=(\"$this_error\") fi lines_read=$((lines_read+1)) fi lines_total=$((lines_total+1)) done \u003c \u003c(tail -n +2 $FILE) if [[ ${#lines_error[@]} -gt 0 ]]; then echo echo \"## NUMBER OF VERIFICATION ERRORS WAS \"$((lines_read-lines_valid))\" ##\" printf '%s\\n' \"${lines_error[@]}\" fi echo echo \"Verification result : verified lines was \"$lines_valid\" of \"$lines_read if [[ $JRE = \"v\" || $lines_valid != $lines_read ]]; then echo exit fi while read LINE || [ -n \"$LINE\" ]; do LINE=$(echo \"${LINE#[[:space:]]}\") LINE=$(echo \"${LINE%[[:space:]]}\") if [[ $LINE =~ ^.+$ ]]; then pos=0 param=\"\" IFS=$DELIMITER read -r -a valueArray \u003c\u003c\u003c \"$LINE\" for element in \"${tags[@]}\"; do value=${valueArray[$pos]} value=$(sed 's/^[[:space:]]*//' \u003c\u003c\u003c \"$value\") value=$(echo \"${value}\" | sed -e 's/\\ *$//g') param=$param\"\\\"$element$PROG_DELIM$value\"\\\"\" \" pos=$((pos+1)) done cmd=($JRE -Djavax.net.ssl.trustStore=$SOAP_KS -jar $SOAP_JAR --key $KEY --props $PROPS --replace $param) result=$(eval ${cmd[@]}) result=$(echo $result | tr -d '\\n') verify=$(echo $result | sed -E 's/^(.+)(Exception|SOAP Document received: )(.+)$/\\2\\3/') if [[ ! \"$result\" == \"$verify\" ]] \u0026\u0026 [[ ! $verify =~ \"HTTP\" ]] \u0026\u0026 [[ ! $verify =~ \"Exception\" ]]; then soap_sent=$((soap_sent+1)) fi fi done \u003c \u003c(tail -n +2 $FILE) echo echo \"Finished processing [ \"$FILE\" ] : lines processed was \"$lines_read echo \"Finished processing [ \"$FILE\" ] : successful calls was \"$soap_sent","title":"Bash - read CSV file","type":"posts"},{"content":"On a recent project, I had a very large XML file which I needed to manipulate. Using the normal DOM method for parsing the XML was very slow. For a file with 8,000 Users, the DOM method took almost 10 minutes. This was clearly unacceptable, and I knew I could improve the speed. That is when I stumbled upon the VTD-XML Java library. It took me quite a long time to get my head around the cursor concept and implementation, but I finally made it to the end of the road. Replacing my DOM code with the VTD-XML code (example below) ended up increasing the speed of XML parsing and manipulation by 8 times.\nFind out more information about the VTD-XML Java library here.\nXML files Original XML file \u0026lt;batchResponse xmlns=\u0026#34;urn:oasis:names:tc:DSML:2:0:core\u0026#34; xmlns:xsd=\u0026#34;http://www.w3.org/2001/XMLSchema\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34;\u0026gt; \u0026lt;searchResultEntry dn=\u0026#34;cn=karble,ou=Users,ou=Active,o=corp\u0026#34;\u0026gt; \u0026lt;attr name=\u0026#34;mail\u0026#34;\u0026gt; \u0026lt;value\u0026gt;TEST_1_Karina.Bleidere@belkast.com\u0026lt;/value\u0026gt; \u0026lt;value\u0026gt;TEST_2_Karina.Bleidere@belkast.com\u0026lt;/value\u0026gt; \u0026lt;/attr\u0026gt; \u0026lt;attr name=\u0026#34;workforceID\u0026#34;\u0026gt; \u0026lt;value\u0026gt;12345\u0026lt;/value\u0026gt; \u0026lt;/attr\u0026gt; \u0026lt;attr name=\u0026#34;sn\u0026#34;\u0026gt; \u0026lt;value\u0026gt;Bleidere\u0026lt;/value\u0026gt; \u0026lt;/attr\u0026gt; \u0026lt;attr name=\u0026#34;co\u0026#34;\u0026gt; \u0026lt;value\u0026gt;Latvia\u0026lt;/value\u0026gt; \u0026lt;/attr\u0026gt; \u0026lt;attr name=\u0026#34;fullName\u0026#34;\u0026gt; \u0026lt;value\u0026gt;Karīna Bleidere\u0026lt;/value\u0026gt; \u0026lt;/attr\u0026gt; \u0026lt;attr name=\u0026#34;givenName\u0026#34;\u0026gt; \u0026lt;value\u0026gt;Karina\u0026lt;/value\u0026gt; \u0026lt;/attr\u0026gt; \u0026lt;attr name=\u0026#34;loginActivationTime\u0026#34;\u0026gt; \u0026lt;value\u0026gt;20180924000000Z\u0026lt;/value\u0026gt; \u0026lt;/attr\u0026gt; \u0026lt;/searchResultEntry\u0026gt; Updated XML file \u0026lt;batchResponse export-count=\u0026#34;1\u0026#34; export-date=\u0026#34;November 12, 2024 7:59:42 PM EST\u0026#34; export-ldap-date=\u0026#34;20241112195942Z\u0026#34; search-filter=\u0026#34;workforceID=12345\u0026#34; xmlns=\u0026#34;urn:oasis:names:tc:DSML:2:0:core\u0026#34; xmlns:xsd=\u0026#34;http://www.w3.org/2001/XMLSchema\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34;\u0026gt; \u0026lt;searchResultEntry src-dn=\u0026#34;\\belkast\\corp\\Active\\Users\\karble\u0026#34; object-count=\u0026#34;1 of 1\u0026#34; dn=\u0026#34;cn=karble,ou=Users,ou=Active,o=corp\u0026#34;\u0026gt; \u0026lt;attr name=\u0026#34;mail\u0026#34; count=\u0026#34;2\u0026#34;\u0026gt; \u0026lt;value\u0026gt;TEST_1_Karina.Bleidere@belkast.com\u0026lt;/value\u0026gt; \u0026lt;value\u0026gt;TEST_2_Karina.Bleidere@belkast.com\u0026lt;/value\u0026gt; \u0026lt;/attr\u0026gt; \u0026lt;attr name=\u0026#34;workforceID\u0026#34;\u0026gt; \u0026lt;value\u0026gt;12345\u0026lt;/value\u0026gt; \u0026lt;/attr\u0026gt; \u0026lt;attr name=\u0026#34;sn\u0026#34;\u0026gt; \u0026lt;value\u0026gt;Bleidere\u0026lt;/value\u0026gt; \u0026lt;/attr\u0026gt; \u0026lt;attr name=\u0026#34;co\u0026#34;\u0026gt; \u0026lt;value\u0026gt;Latvia\u0026lt;/value\u0026gt; \u0026lt;/attr\u0026gt; \u0026lt;attr name=\u0026#34;fullName\u0026#34;\u0026gt; \u0026lt;value convert-from=\u0026#34;Karīna Bleidere\u0026#34; xsi:type=\u0026#34;xsd:base64Binary\u0026#34;\u0026gt;S2FyxKtuYSBCbGVpZGVyZQ==\u0026lt;/value\u0026gt; \u0026lt;/attr\u0026gt; \u0026lt;attr name=\u0026#34;givenName\u0026#34;\u0026gt; \u0026lt;value\u0026gt;Karina\u0026lt;/value\u0026gt; \u0026lt;/attr\u0026gt; \u0026lt;attr name=\u0026#34;loginActivationTime\u0026#34;\u0026gt; \u0026lt;value convert-from=\u0026#34;20180924000000Z\u0026#34; format-from=\u0026#34;yyyyMMddHHmmss\u0026#34; format-to=\u0026#34;yyyy-MM-dd\u0026#34;\u0026gt;2018-09-24\u0026lt;/value\u0026gt; \u0026lt;/attr\u0026gt; \u0026lt;/searchResultEntry\u0026gt; Javascript function function checkXML ( thisFile, thisType, thisAttribute, thisSearch, thisFilter, thisDates ) { if ( thisFile == undefined || thisType != \u0026#34;dsml\u0026#34; ) { return []; } try { fileReader = new FileReader(thisFile); vtdGen = new VTDGen(); vtdGen.parseFile(thisFile, true); vtdNav = vtdGen.getNav(); xm = new XMLModifier(vtdNav); } catch (e) { varError.push(\u0026#34;[ checkXML, XML file reader ] : \u0026#34; + thisFile + \u0026#34; =\u0026gt; \u0026#34; + e); return [] } rootPilot = new AutoPilot(vtdNav); rootPilot.selectXPath( \u0026#34;count(./*)\u0026#34; ); count = rootPilot.evalXPathToNumber(); rootPilot.selectXPath( \u0026#34;/*/*[1]/@dn\u0026#34; ); varUser = rootPilot.evalXPathToString(); var varValue = thisSearch.get(varUser)[0].get (\u0026#34;meta-date\u0026#34;); var varHeader = new Array; varHeader.push(\u0026#34;\u0026#34;); varHeader.push(\u0026#39;export-count=\u0026#34;\u0026#39; + count + \u0026#39;\u0026#34;\u0026#39;); varHeader.push(\u0026#39;export-date=\u0026#34;\u0026#39; + new Date().toLocaleString() + \u0026#39;\u0026#34;\u0026#39;); varHeader.push(\u0026#39;export-ldap-date=\u0026#34;\u0026#39; + varValue.toArray()[0] + \u0026#39;\u0026#34;\u0026#39;); thisFilter = java.lang.String(thisFilter).replace(\u0026#34;\u0026amp;\u0026#34;, \u0026#34;\u0026amp;amp;\u0026#34;); thisFilter = java.lang.String(thisFilter).replace(\u0026#34;\u0026lt;\u0026#34;, \u0026#34;\u0026amp;lt;\u0026#34;); thisFilter = java.lang.String(thisFilter).replace(\u0026#34;\u0026gt;\u0026#34;, \u0026#34;\u0026amp;gt;\u0026#34;); thisFilter = java.lang.String(thisFilter).replace(\u0026#39;\u0026#34;\u0026#39;, \u0026#34;\u0026amp;pos;\u0026#34;); thisFilter = java.lang.String(thisFilter).replace(\u0026#34;\u0026#39;\u0026#34;, \u0026#34;\u0026amp;quot;\u0026#34;); varHeader.push(\u0026#39;search-filter=\u0026#34;\u0026#39; + thisFilter + \u0026#39;\u0026#34;\u0026#39;); xm.insertAttribute(varHeader.join(\u0026#34; \u0026#34;)); userPilot = new AutoPilot(vtdNav); userPilot.selectXPath( \u0026#34;/*/*\u0026#34; ); while (userPilot.evalXPath() != -1 ) { var varTags = new Array(); xPathNodes = new AutoPilot(vtdNav) xPathNodes.selectXPath( \u0026#34;./@dn\u0026#34; ); varUser = xPathNodes.evalXPathToString(); var varValue = thisSearch.get(varUser)[0].get (\u0026#34;meta-edir\u0026#34;); varTags.push(\u0026#34;\u0026#34;); varTags.push(\u0026#39;src-dn=\u0026#34;\u0026#39; + replaceInMe(varValue.toArray()[0], \u0026#34;\\\\\\\\\u0026#34;, \u0026#34;\\\\\u0026#34;) + \u0026#39;\u0026#34;\u0026#39;); xPathNodes.selectXPath(\u0026#34;count(preceding-sibling::*)\u0026#34;); count = Number(xPathNodes.evalXPathToString()) + 1 + \u0026#34; of \u0026#34; + varSearch.size(); varTags.push(\u0026#39;object-count=\u0026#34;\u0026#39; + count + \u0026#39;\u0026#34;\u0026#39;); xm.insertAttribute(varTags.join(\u0026#34; \u0026#34;)); } attrPilot = new AutoPilot(vtdNav); attrPilot.selectXPath( \u0026#34;/*/*/*\u0026#34; ); while ( (result = attrPilot.evalXPath()) != -1 ) { var varTags = new Array(); varTags.push(\u0026#34;\u0026#34;); xPathNodes = new AutoPilot(vtdNav) xPathNodes.selectXPath(\u0026#34;count(./*)\u0026#34;); varCount = xPathNodes.evalXPathToNumber(); if (varCount \u0026gt; 1) { varTags.push(\u0026#39;count=\u0026#34;\u0026#39; + varCount + \u0026#39;\u0026#34;\u0026#39;); xm.insertAttribute(varTags.join(\u0026#34; \u0026#34;)); } } datePilot = new AutoPilot(vtdNav); datePilot.selectXPath( \u0026#34;/*/*/attr/value\u0026#34; ); while ( (result = datePilot.evalXPath()) != -1 ) { var varTags = new Array(); varTags.push(\u0026#34;\u0026#34;); var varMe = undefined; xPathNodes = new AutoPilot(vtdNav) xPathNodes.selectXPath(\u0026#34;../@name\u0026#34;); var varLDAPAttrName = xPathNodes.evalXPathToString(); xPathNodes1 = new AutoPilot(vtdNav) xPathNodes1.bind(vtdNav); xPathNodes1.selectXPath(\u0026#34;./text()\u0026#34;); varValue = xPathNodes1.evalXPathToString(); var varContains = thisDates.get(varLDAPAttrName); if ( varContains \u0026amp;\u0026amp; varContains.length \u0026gt; 0 ) { var varIn = varContains[0].split(\u0026#34;\\\\|\u0026#34;)[0]; var varOut = varContains[0].split(\u0026#34;\\\\|\u0026#34;)[1]; var varMe = convertDate ( String(varValue), varIn, varOut ); varTags.push(\u0026#39;convert-from=\u0026#34;\u0026#39; + varValue + \u0026#39;\u0026#34;\u0026#39;); varTags.push(\u0026#39;format-from=\u0026#34;\u0026#39; + varIn + \u0026#39;\u0026#34;\u0026#39;); varTags.push(\u0026#39;format-to=\u0026#34;\u0026#39; + varOut + \u0026#39;\u0026#34;\u0026#39;); } if (!com.novell.ldap.util.Base64.isLDIFSafe(varValue)) { var varMe = com.novell.ldap.util.Base64.encode(varValue); varTags.push(\u0026#39;convert-from=\u0026#34;\u0026#39; + varValue + \u0026#39;\u0026#34;\u0026#39;); varTags.push(\u0026#39;xsi:type=\u0026#34;xsd:base64Binary\u0026#34;\u0026#39;); } if ( varMe \u0026amp;\u0026amp; varMe != undefined ) { xm.updateToken(vtdNav.getText(), varMe); } if ( varTags.length \u0026gt; 0 ) { xm.insertAttribute(varTags.join(\u0026#34; \u0026#34;)); } } xm.output( thisFile ); } ","date":"12 November 2024","externalUrl":null,"permalink":"/posts/code/xml/","section":"Posts","summary":"On a recent project, I had a very large XML file which I needed to manipulate. Using the normal DOM method for parsing the XML was very slow. For a file with 8,000 Users, the DOM method took almost 10 minutes. This was clearly unacceptable, and I knew I could improve the speed. That is when I stumbled upon the VTD-XML Java library. It took me quite a long time to get my head around the cursor concept and implementation, but I finally made it to the end of the road. Replacing my DOM code with the VTD-XML code (example below) ended up increasing the speed of XML parsing and manipulation by 8 times.\n","title":"Use VTD-XML to parse XML","type":"posts"},{"content":" C-g shows file name g~~ reverse the case zz position line in the middle of the screen without moving cursor ","date":"7 November 2024","externalUrl":null,"permalink":"/posts/linux/vim/","section":"Posts","summary":" C-g shows file name g~~ reverse the case zz position line in the middle of the screen without moving cursor ","title":"Useful VIM commands","type":"posts"},{"content":"","date":"1 November 2024","externalUrl":null,"permalink":"/tags/idm/","section":"Tag","summary":"","title":"IDM","type":"tags"},{"content":"NetIQ eDirectory is officially supported on RedHat, SuSE, or Windows; but these are all paid operating systems and who doesn\u0026rsquo;t like a free alternative? In the past, NetIQ eDirectory would install on CentOS with no configuration changes; but that operating system is End Of Life and is therefore no longer supported. Using these instructions, you will be able to install NetIQ eDirectory on the free RedHat derivative Alma Linux. Alma Linux is a 100% binary compatible fork of RedHat, and therefore NetIQ eDirectory will run beautifully on it. We have tested this configuration with Alma Linux 9.4 and NetIQ eDirectory 9.2.9. It\u0026rsquo;s very straightforward:\nCreate a spec file Install rpm-build Build the spec file Install the rpm file Instructions # nano redhat-release.spec (copy and paste the file contents from the section below) dnf install rpm-build rpmbuild -bb redhat-release.spec dnf localinstall ./rpmbuild/RPMS/x86_64/redhat-release-server-9.4.1911-1.el9.x86_64.rpm redhat-release.spec # Name: redhat-release-server Version: 9.4.1911 Release: 1%{?dist} Summary: RedHat Release Dummy Package Group: Networking/Daemons License: No Licence URL: http://www.mysite.com %description This is an empty dummy package to satisfy a dependency. %files %changelog * Fri Nov 01 2024 Belkast Consulting - Initial release ","date":"1 November 2024","externalUrl":null,"permalink":"/posts/belkast/edirectory/","section":"Posts","summary":"NetIQ eDirectory is officially supported on RedHat, SuSE, or Windows; but these are all paid operating systems and who doesn’t like a free alternative? In the past, NetIQ eDirectory would install on CentOS with no configuration changes; but that operating system is End Of Life and is therefore no longer supported. Using these instructions, you will be able to install NetIQ eDirectory on the free RedHat derivative Alma Linux. Alma Linux is a 100% binary compatible fork of RedHat, and therefore NetIQ eDirectory will run beautifully on it. We have tested this configuration with Alma Linux 9.4 and NetIQ eDirectory 9.2.9. It’s very straightforward:\n","title":"NetIQ eDirectory on Alma Linux","type":"posts"},{"content":"The script shown below can be run via a CRON job (we run it every 15 minutes). It checks the Nextcloud apache2 log and sends out an email if a Share has been accessed since the last time the CRON job ran. The curl command which sends out the email uses the free Twillo SendGrid service,\n#!/usr/bin/env bash # FILE=\u0026#34;/home/karmst/Documents/nextcloud_access.log\u0026#34; mapfile -t my_array \u0026lt; \u0026lt;( grep -E \u0026#34;^.+GET /s/.+\u0026#34; /var/log/apache2/sites/nextcloud_access.log ) counter=0 for value in \u0026#34;${my_array[@]}\u0026#34; do to_check=$(echo \u0026#34;$value\u0026#34; | sed -E \u0026#39;s/(^.+\\[.+\\].+)( .+)$/\\1/\u0026#39;) share_name=$(echo \u0026#34;$value\u0026#34; | sed -E \u0026#39;s/(^.+GET )(.+) (HTTP.+)$/\\2/\u0026#39;) result=$(echo \u0026#34;$value\u0026#34; | sed -E \u0026#39;s/(^.+GET )(.+?) (HTTP.+?)\u0026#34;\\s([0-9]{3})(.+$)/\\4/\u0026#39;) to_check_replace_1=${to_check//[/\\\\[} to_check_replace_2=${to_check_replace_1//]/\\\\]} downloads_file_check=$( grep \u0026#34;$to_check_replace_2\u0026#34; $FILE) if [ -z \u0026#34;$downloads_file_check\u0026#34; ]; then counter=$((counter+1)) downloads_to_file=$( echo \u0026#34;$value\u0026#34; \u0026gt;\u0026gt; $FILE ) IP=$(echo \u0026#34;$value\u0026#34; | sed -E \u0026#39;s/(^.+) - - (.+)$/\\1/\u0026#39;) DATE=$(echo \u0026#34;$value\u0026#34; | sed -E \u0026#39;s/^.+(\\[.+\\])(.+)$/\\1/\u0026#39;) LOCATION=$(curl ipinfo.io/\u0026#34;$IP\u0026#34;/city) THECONTENT+=$(echo Date : $DATE\u0026#34;\u0026lt;br\u0026gt;\u0026#34;Location : $LOCATION\u0026#34;\u0026lt;br\u0026gt;\u0026#34;Address : $IP\u0026#34;\u0026lt;br\u0026gt;\u0026#34;Share : $share_name\u0026#34;\u0026lt;br\u0026gt;\u0026#34;Result : $result\u0026#34;\u0026lt;br\u0026gt;\u0026lt;br\u0026gt;\u0026#34;) fi done SUBJECT=$(echo Belkast fileshare was accessed $counter time\\(s\\)) if [ -n \u0026#34;$THECONTENT\u0026#34; ]; then curl --request POST \\ --url https://api.sendgrid.com/v3/mail/send \\ --header \u0026#39;Authorization: Bearer API-KEY\u0026#39; \\ --header \u0026#39;Content-Type: application/json\u0026#39; \\ --data \u0026#39;{\u0026#34;personalizations\u0026#34;: [{\u0026#34;to\u0026#34;: [{\u0026#34;email\u0026#34;: \u0026#34;test@example.com\u0026#34;}]}],\u0026#34;from\u0026#34;: {\u0026#34;email\u0026#34;: \u0026#34;admin@example.com\u0026#34;},\u0026#34;subject\u0026#34;: \u0026#39;\\\u0026#34;\u0026#34;$SUBJECT\\\u0026#34;\u0026#34;\u0026#39;,\u0026#34;content\u0026#34;: [{\u0026#34;type\u0026#34;: \u0026#34;text/html\u0026#34;, \u0026#34;value\u0026#34;: \u0026#39;\\\u0026#34;\u0026#34;$THECONTENT\\\u0026#34;\u0026#34;\u0026#39;}]}\u0026#39; fi exit 0; The content of the email is shown below. Only one email is sent, regardless of how many times the Share was accessed.\nDate : [22/Oct/2024:13:33:50 +0100] Location : Boston Address : 172.172.172.1 Share : /s/36Tm5RS38bJ5D5wfgB Result : 404 ​ Date : [22/Oct/2024:15:58:53 +0100] Location : Boston Address : 172.172.172.1 Share : /s/36Tm5RS38bJ5D5wfgB Result : 404 ​ Date : [22/Oct/2024:16:36:50 +0100] Location : Boston Address : 172.172.172.1 Share : /s/36Tm5RS38bJ5D5wfgB Result : 404 ","date":"23 October 2024","externalUrl":null,"permalink":"/posts/linux/emails/","section":"Posts","summary":"The script shown below can be run via a CRON job (we run it every 15 minutes). It checks the Nextcloud apache2 log and sends out an email if a Share has been accessed since the last time the CRON job ran. The curl command which sends out the email uses the free Twillo SendGrid service,\n","title":"Nextcloud Share access","type":"posts"},{"content":"Posts related to the i3 Window Manager on Linux.\n","date":"15 October 2024","externalUrl":null,"permalink":"/series/i3/","section":"Series","summary":"Posts related to the i3 Window Manager on Linux.\n","title":"i3 Window Manager","type":"series"},{"content":"Move to the next / previous workspace This Bash script allows you to move the focused window to the next or previous i3 workspace. This is very useful, as it allows you to get a tiled windo\u0026gt;\nIn your i3 configuration file, put the following two lines:\nbindsym $super+period exec ~/scripts/i3/window.sh bindsym $super+comma exec ~/scripts/i3/window.sh back #!/bin/bash OLD_IFS=\u0026#34;$IFS\u0026#34; IFS=$\u0026#39;\\n\u0026#39; win_array=($(i3-msg -t get_config | grep \u0026#39;set $workspace\u0026#39; | sed \u0026#39;s/^.\\+ .\\+\\(\u0026#34;.\\+\\)$/\\1/\u0026#39;)) IFS=\u0026#34;$OLD_IFS\u0026#34; win_length=${#win_array[@]} #echo $win_array #echo $win_length move=1 max=\u0026#34;$((win_length-1))\u0026#34; next_max=-1 if [ \u0026#34;$1\u0026#34; == \u0026#39;back\u0026#39; ] then move=-1 max=0 next_max=\u0026#34;$(win_length)\u0026#34; fi wsCurrent=$( i3-msg -t get_workspaces | jq \u0026#39;.[] | select(.focused).name\u0026#39; ) #echo $wsCurrent for i in \u0026#34;${!win_array[@]}\u0026#34;; do if [[ \u0026#34;${win_array[$i]}\u0026#34; = \u0026#34;${wsCurrent}\u0026#34; ]]; then myWin=${i}; fi done #echo $myWin if [ $myWin -eq $max ] then myWin=$next_max fi newWin_index=$(($myWin + $move)) newWin=${win_array[$newWin_index]} i3-msg move container workspace $newWin, workspace $newWin exit 0 ","date":"15 October 2024","externalUrl":null,"permalink":"/series/i3/windows/","section":"Series","summary":"Move to the next / previous workspace This Bash script allows you to move the focused window to the next or previous i3 workspace. This is very useful, as it allows you to get a tiled windo\u003e\n","title":"Move a window in i3","type":"series"},{"content":"The script below determines the Linux distribution based on the /etc/os-release file. Using an if statement block, the appropriate command for that distribution is set and, finally, executed.\n#!/usr/bin/env bash me=$(cat /etc/os-release |grep -m 1 NAME | sed -E \u0026#39;s/^NAME=(.+)$/\\1/\u0026#39;) if [[ $me == \u0026#39;\u0026#34;Void\u0026#34;\u0026#39; ]]; then upd=\u0026#39;sudo xbps-install -Su\u0026#39; elif [[ $me == \u0026#39;ArcoLinux\u0026#39; ]]; then upd=\u0026#39;sudo pacman -Suuy\u0026#39; fi $upd In your shell rc file simply assign an alias to the bash script above. Since I have laptops with VOID Linux and Arco Linux, it works well.\n","date":"22 September 2024","externalUrl":null,"permalink":"/posts/linux/update/","section":"Posts","summary":"The script below determines the Linux distribution based on the /etc/os-release file. Using an if statement block, the appropriate command for that distribution is set and, finally, executed.\n#!/usr/bin/env bash me=$(cat /etc/os-release |grep -m 1 NAME | sed -E 's/^NAME=(.+)$/\\1/') if [[ $me == '\"Void\"' ]]; then upd='sudo xbps-install -Su' elif [[ $me == 'ArcoLinux' ]]; then upd='sudo pacman -Suuy' fi $upd In your shell rc file simply assign an alias to the bash script above. Since I have laptops with VOID Linux and Arco Linux, it works well.\n","title":"Update packages across distros","type":"posts"},{"content":"The script below shows how to use xinput and sed to enable and disable the trackpad and, when the script is assigned to a shortcut key, it makes it quite handy. The script uses the sed pattern \u0026ldquo;s/(.+)(id=)(.+)[(.+)/\\3/\u0026quot; to retrieve the correct trackpad ID. Please note that you will need the following programs installed before the script will run:\nsed awk xinput dunst #!/usr/bin/env bash me=$(uname -a | awk \u0026#39;{print $2}\u0026#39;) if [[ $me == \u0026#39;void-linux\u0026#39; ]] || [[ $me == \u0026#39;CHROMEBOOK\u0026#39; ]] || [[ $me == \u0026#39;dell\u0026#39; ]]; then searcher=\u0026#39;Touchpad\u0026#39; else searcher=\u0026#39;bcm5974\u0026#39; fi myID=$(xinput list | grep $searcher | sed -E \u0026#34;s/(.+)(id=)(.+)\\[(.+)/\\3/\u0026#34;) state=$(xinput list-props $myID | grep \u0026#39;Device Enabled\u0026#39; | sed \u0026#39;s/^\\(.\\+\\)Enabled.\\+\\(.\\+\\)$/\\2/\u0026#39;) if [[ ($state == \u0026#39;1\u0026#39;) ]]; then xinput disable $myID dunstify \u0026#34;TrackPad is disabled\u0026#34; else xinput enable $myID dunstify \u0026#34;TrackPad is enabled\u0026#34; fi ","date":"18 September 2024","externalUrl":null,"permalink":"/posts/linux/mouse/","section":"Posts","summary":"The script below shows how to use xinput and sed to enable and disable the trackpad and, when the script is assigned to a shortcut key, it makes it quite handy. The script uses the sed pattern “s/(.+)(id=)(.+)[(.+)/\\3/\" to retrieve the correct trackpad ID. Please note that you will need the following programs installed before the script will run:\n","title":"Bash - enable / disable trackpad","type":"posts"},{"content":"You encounter the following issue : after a fresh install of Arch Linux on a Chromebook, the system does not boot. The system boot process gets to the initial Linux GRUB screen but for some reason the internal disk is not recognized. It could be because the Chromebook has a UFS disk and therefore, to fix this issue, you need to add the UFS modules to the initramfs image. Assume you have the partitions listed below.\n/dev/sdb1 -\u0026gt; /boot/efi /dev/sdb2 -\u0026gt; / /dev/sdb3 -\u0026gt; /home First of all, boot the system from a live ISO. Once the live ISO is booted, you can drop to a terminal. Then complete the steps below:\nCreate a new directory to hold the root filesystem. In this example it is /mnt/root Mount the partitions listed above (yours will most likely be different) Mount /dev Mount /proc Mount /sys arch-chroot to the previously created directory These steps are required before you can generate the new initramfs. Of course, don't forget to create the ufs.conf file! sudo su mkdir /mnt/root mount /dev/sdb2 /mnt/root mount /dev/sdb1 /mnt/root/boot/efi mount /dev/sdb3 /mnt/root/home mount --bind /dev /mnt/root/dev mount --bind /proc /mnt/root/proc mount --bind /sys /mnt/root/sys cd / arch-chroot /mnt/root Before generating the new initramfs, you will need to create a new configuration file ufs.conf which will add the modules that need to be loaded. The contents of the /etc/mkinitcpio.conf.d/ufs.conf file are shown below.\nMODULES=(ufs ufshcd-pci) Now you can go ahead and generate the new initramfs. For my Arco Linux installation I had to generate two initramfs, as shown below.\nmkinitcpio -p linux mkinitcpio -p linux-zen -rw------- 1 root root 16896416 Sep 15 17:22 initramfs-linux.img -rw------- 1 root root 17172530 Sep 15 17:22 initramfs-linux-zen.img Exit out of the chroot environment and reboot the system. The UFS disk should now be detected.\n","date":"16 September 2024","externalUrl":null,"permalink":"/posts/linux/ufs/","section":"Posts","summary":"You encounter the following issue : after a fresh install of Arch Linux on a Chromebook, the system does not boot. The system boot process gets to the initial Linux GRUB screen but for some reason the internal disk is not recognized. It could be because the Chromebook has a UFS disk and therefore, to fix this issue, you need to add the UFS modules to the initramfs image. Assume you have the partitions listed below.\n","title":"UFS module and initramfs","type":"posts"},{"content":"If you have installed Linux using a base image and not a GUI installer, and therefore need to connect to a WiFi network after the first boot, you can do so by following the steps outlined below. Start by running wpa_cli.\nscan scan_results add_network set_network 0 ssid \u0026#34;SSID\u0026#34; set_network 0 psk \u0026#34;PSK\u0026#34; enable_network 0 save_config quit Now run dhcpcd.\n","date":"15 September 2024","externalUrl":null,"permalink":"/posts/linux/wpa/","section":"Posts","summary":"If you have installed Linux using a base image and not a GUI installer, and therefore need to connect to a WiFi network after the first boot, you can do so by following the steps outlined below. Start by running wpa_cli.\n","title":"WPA supplicant (wpa_cli)","type":"posts"},{"content":"The list below gives you an idea of the software that we use here at Belkast Consulting.\nNextCloud The most popular open source content collaboration platform for tens of millions of users at thousands of organizations across the globe. Click Here Kimai Time Tracker An open-source platform designed to keep track of and analyze your company\u0026#39;s time-data, making it perfect for businesses of all sizes. Kimai helps you keep an eye on time and money. Click Here Suite CRM SuiteCRM is the award-winning open-source, enterprise-ready Customer Relationship Management (CRM) software application. Click Here Paperless NGX Paperless-ngx is a community-supported open-source document management system that transforms your physical documents into a searchable online archive so you can keep, well, less paper. Click Here Private Bin PrivateBin is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Click Here IntelliJ IDEA Community Edition The IDE for Java and Kotlin enthusiasts. Click Here Gitea The goal of this project is to make the easiest, fastest, and most painless way of setting up a self-hosted Git service. Click Here Hugo Website generator The world’s fastest framework for building websites. Hugo is one of the most popular open-source static site generators. With its amazing speed and flexibility, Hugo makes building websites fun again. Click Here ","date":"12 September 2024","externalUrl":null,"permalink":"/software/","section":"Posts","summary":"The list below gives you an idea of the software that we use here at Belkast Consulting.\nNextCloud The most popular open source content collaboration platform for tens of millions of users at thousands of organizations across the globe. Click Here Kimai Time Tracker An open-source platform designed to keep track of and analyze your company's time-data, making it perfect for businesses of all sizes. Kimai helps you keep an eye on time and money. Click Here Suite CRM SuiteCRM is the award-winning open-source, enterprise-ready Customer Relationship Management (CRM) software application. Click Here Paperless NGX Paperless-ngx is a community-supported open-source document management system that transforms your physical documents into a searchable online archive so you can keep, well, less paper. Click Here Private Bin PrivateBin is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Click Here IntelliJ IDEA Community Edition The IDE for Java and Kotlin enthusiasts. Click Here Gitea The goal of this project is to make the easiest, fastest, and most painless way of setting up a self-hosted Git service. Click Here Hugo Website generator The world’s fastest framework for building websites. Hugo is one of the most popular open-source static site generators. With its amazing speed and flexibility, Hugo makes building websites fun again. Click Here ","title":"Software List","type":"dummy"},{"content":"Posts related to JavaScript / ECMAScript language.\n","date":"20 July 2024","externalUrl":null,"permalink":"/series/ecmascript/","section":"Series","summary":"Posts related to JavaScript / ECMAScript language.\n","title":"ECMAScript","type":"series"},{"content":" Miscellaneous Functions # Array of folders in a directory # function getFoldersInDirectory(directoryPath) { var varDirs = new Array(); var varDirectory = new File(directoryPath); var varFiles = varDirectory.listFiles(); for (var ff = 0; ff \u0026lt; varFiles.length; ff++) { if (varFiles[ff].isDirectory()) { varDirs.push(varFiles[ff]); } } return varDirs; } Array of files in a directory # function getFilesInDirectory(directoryPath) { var varDirs = new Array(); var varDirectory = new File(directoryPath); var varFiles = varDirectory.listFiles(); for (var ff = 0; ff \u0026lt; varFiles.length; ff++) { if (varFiles[ff].isFile()) { varDirs.push(varFiles[ff]); } } return varDirs; } Check if a file exists in a directory # function isFileInDirectory(file, directoryPath) { var varFile = new File(directoryPath + file); return varFile.exists(); } ","date":"20 July 2024","externalUrl":null,"permalink":"/series/ecmascript/misc/","section":"Series","summary":"Miscellaneous Functions # Array of folders in a directory # function getFoldersInDirectory(directoryPath) { var varDirs = new Array(); var varDirectory = new File(directoryPath); var varFiles = varDirectory.listFiles(); for (var ff = 0; ff \u003c varFiles.length; ff++) { if (varFiles[ff].isDirectory()) { varDirs.push(varFiles[ff]); } } return varDirs; } Array of files in a directory # function getFilesInDirectory(directoryPath) { var varDirs = new Array(); var varDirectory = new File(directoryPath); var varFiles = varDirectory.listFiles(); for (var ff = 0; ff \u003c varFiles.length; ff++) { if (varFiles[ff].isFile()) { varDirs.push(varFiles[ff]); } } return varDirs; } Check if a file exists in a directory # function isFileInDirectory(file, directoryPath) { var varFile = new File(directoryPath + file); return varFile.exists(); }","title":"Miscellaneous functions","type":"series"},{"content":" Array Functions # Convert ArrayList to Array # importPackage(java.util); importClass(java.util.ArrayList); var varArrayList = new ArrayList(); varArrayList.add ( \u0026#34;Item 1\u0026#34; ); varArrayList.add ( \u0026#34;Item 2\u0026#34; ); varArrayList.add ( \u0026#34;Item 1\u0026#34; ); print (); print ( varArrayList ); print ( arrayListConvert (varArrayList) ); function arrayListConvert (arrayList) { var varArray = new Array(); for ( var ee = 0; ee \u0026lt; arrayList.size(); ee++ ) { varArray.push ( arrayList[ee] ) } return varArray; } Given the Array List of [Item 1, Item 2, Item 1], the function returns the Array Item 1,Item 2,Item 1\nRemove duplicates # varArrayList = new ArrayList ( new HashSet ( varArrayList ) ); varArrayList = arrayListConvert( varArrayList ); varArrayList.sort() print ( varArrayList ) Given the Array List of [Item 1, Item 2, Item 1], the function returns an Array which can then be sorted to give Item 1,Item 2\n","date":"19 July 2024","externalUrl":null,"permalink":"/series/ecmascript/arrays/","section":"Series","summary":"Array Functions # Convert ArrayList to Array # importPackage(java.util); importClass(java.util.ArrayList); var varArrayList = new ArrayList(); varArrayList.add ( \"Item 1\" ); varArrayList.add ( \"Item 2\" ); varArrayList.add ( \"Item 1\" ); print (); print ( varArrayList ); print ( arrayListConvert (varArrayList) ); function arrayListConvert (arrayList) { var varArray = new Array(); for ( var ee = 0; ee \u003c arrayList.size(); ee++ ) { varArray.push ( arrayList[ee] ) } return varArray; } Given the Array List of [Item 1, Item 2, Item 1], the function returns the Array Item 1,Item 2,Item 1\n","title":"Arrays and Collections","type":"series"},{"content":" Encryption and Decryption # Generate the keys # openssl genrsa -out mykey.pem 2048 openssl rsa -in mykey.pem -pubout \u003e public_key.pem openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in mykey.pem -out private_key.pem doEncrypt # importPackage(java.io); importPackage(java.util); importClass(java.util.Base64); importClass(java.security.cert.CertificateFactory); importClass(java.security.cert.X509Certificate); importClass(java.security.KeyStore); importClass(java.security.spec.X509EncodedKeySpec); importPackage(java.security); importPackage(java.security.interfaces); importPackage(java.security.spec); importPackage(Packages.javax.crypto); importPackage(Packages.javax.crypto.interfaces); importPackage(Packages.javax.crypto.spec); importPackage(Packages.javax.security.cert); var JString = java.lang.String; function doEncrypt ( password, file ) { try { var varCheck = (checkRegex ( password, \u0026#34;^.+$\u0026#34;) ); myFile = new File (file) key = new JString(Files.readAllBytes(myFile.toPath())); } catch (e) { return ( new JString() ); } publicKey = key.replace(\u0026#34;-----BEGIN PUBLIC KEY-----\u0026#34;, \u0026#34;\u0026#34;).replaceAll(System.lineSeparator(), \u0026#34;\u0026#34;).replace(\u0026#34;-----END PUBLIC KEY-----\u0026#34;, \u0026#34;\u0026#34;); encoded = Base64.getDecoder().decode(publicKey); keyfactory = KeyFactory.getInstance(\u0026#34;RSA\u0026#34;); keySpec = new X509EncodedKeySpec(encoded); publicKey = keyfactory.generatePublic(keySpec); encrypt_cipher = Packages.javax.crypto.Cipher.getInstance(\u0026#34;RSA\u0026#34;); encrypt_cipher.init(Packages.javax.crypto.Cipher.ENCRYPT_MODE, publicKey); ciphertext = encrypt_cipher.doFinal(new JString(password).getBytes()); var encoded = Base64.getEncoder().encodeToString(ciphertext).getBytes(); return ( new JString(encoded) ); } var varCerts = new Array(); varCerts.push ( \u0026#34;./public_key.pem\u0026#34; ); varCerts.push ( \u0026#34;./private_key.pem\u0026#34; ); var varPassword = \u0026#34;MyPassword\u0026#34;; varEncrypted = doEncrypt ( varPassword, varCerts[0] ); doDecrypt # importPackage(java.io); importPackage(java.util); importClass(java.util.Base64); importClass(java.security.cert.CertificateFactory); importClass(java.security.cert.X509Certificate); importClass(java.security.KeyStore); importClass(java.security.spec.X509EncodedKeySpec); importPackage(java.security); importPackage(java.security.interfaces); importPackage(java.security.spec); importPackage(Packages.javax.crypto); importPackage(Packages.javax.crypto.interfaces); importPackage(Packages.javax.crypto.spec); importPackage(Packages.javax.security.cert); var JString = java.lang.String; function doDecrypt ( password, file ) { try { myFile = new File (file); key = new JString(Files.readAllBytes(myFile.toPath())); } catch (e) { return ( new JString() ); } privateKey = key.replace(\u0026#34;-----BEGIN PRIVATE KEY-----\u0026#34;, \u0026#34;\u0026#34;).replaceAll(System.lineSeparator(), \u0026#34;\u0026#34;).replace(\u0026#34;-----END PRIVATE KEY-----\u0026#34;, \u0026#34;\u0026#34;); encoded = Base64.getDecoder().decode(privateKey); keyfactory = KeyFactory.getInstance(\u0026#34;RSA\u0026#34;); keySpec = new PKCS8EncodedKeySpec(encoded); privateKey = keyfactory.generatePrivate(keySpec); decrypt_cipher = Packages.javax.crypto.Cipher.getInstance(\u0026#34;RSA\u0026#34;); decrypt_cipher.init(Packages.javax.crypto.Cipher.DECRYPT_MODE, privateKey); try { decoded = Base64.getDecoder().decode(password); ciphertext = decrypt_cipher.doFinal(decoded); return ( new JString(ciphertext) ); } catch (e) { return ( new JString() ); } } var varCerts = new Array(); varCerts.push ( \u0026#34;./public_key.pem\u0026#34; ); varCerts.push ( \u0026#34;./private_key.pem\u0026#34; ); var varPassword = \u0026#34;Ou0b5H7ha/sVfqK+gA2hCPhs7c8qeGSTKkMAcyCH2vSjb8CfvfeE8d+NbDwzveYPxC4Rh5GNR0CiQaAZPIiIqV4DMv/i2ELhcjPeI9VWloX5OMrMa5VVoyWElRh93N+D1XP7j+N9ogxRstObLQS1C+uczFmLDEIkWvwNdEM8O7DdkKvgaULiw4r4kfV3ROM0Uu44hxsFTp8V3pIR9ncrkK27ZEE4SGsd1BtimxACNgzqc3fGXxICU4VRDgP0oSSHO35d8ouHt8raAmdVWfKFYqaIWgP9/RSmooHTCaQBfb1tCCLxXrEkEZa47bTIXyIs2I4pBc02OGrDdNMQtAbUWA==\u0026#34;; varDecrypted = doDecrypt ( varPassword, varCerts[1] ); As an example, I have wrapped the doEncrypt and doDecrypt functions in a separate class. Notice that the two encrypted values are different, due to the fact that the values to encrypt are encrypted using a Public Key.\n./doClassM.sh \u0026ndash;encrypt Please enter the value to encrypt : myPassword$ Value to encrypt : myPassword$ Encrypted value : Gcr+uPEZ2F7prfiF4+YihrOhan4D+CaIBDYILep7xtMMOpBZAUtOC0+gPDunFI6QAGFFFYBa2gnwUySxTDDxJsSp++XwwFFJb7MMA9TjuYJXFdCCiE7EZNIwnTI7fniO0eYJi7VOyN4eB9Wb6wCNj6ug4ZRBMJFTphj+N6ukK8WQSmJUg8yLifnc3zn4d6Vz7uP7D2lGofeK2VTkOVuWN8TZbaMvqI+flCP5Ed8yUzr8hfDqCfTJ8KlAHKXGQDlidg0uBrC8DA9/nPisUi6h7cx4uM+hTbNnOO7r/FyadUCOFsIfcQXMs3iEOlGS1J53/jaDOn03XkVf/1jah71yMA== Decrypted value : myPassword$\n./doClassM.sh \u0026ndash;encrypt Please enter the value to encrypt : myPassword$ Value to encrypt : myPassword$ Encrypted value : eKUVpeJxsr5jUkfZq78k/zeC1vaFvORpSzkszzQYuK8tUwP0/FikM1Vr7wx34EZh+0ho3ByztZ11btYPOASiQpgHCa+TXwB6nVce4uSwuKfqqGOXaW9wpiR/R5roORr3LULkdPuU1a22JkYjziEDmjVF24CKtXOsoeZhxLwZV6VAN/v+4I2erBs5OqI+aDczsvCtnQ3vFL8kE7l6iDdYktZ6vlKtuX7CKsh22Qu9lHjtFzGSZ1P5XQVtDGXYgrkT+h6pkrw9dzVIhLgdfPf4rkfTzMTMfj0TdwbeOWsK9o5UMha64TAweUR1ZGcj/aLat+bQCqdufBkHsbNamakKqQ== Decrypted value : myPassword$\nThe encrypted value can be safely stored in a configuration file, and if the decryption code and the private key are both secure you are safe in the knowledge that the actual values behind the encrypted ones are safe.\nI use Mozilla Rhino for all my cross-platform code projects, and I compile the source code in to a Java class file.\n#!/bin/bash java -cp \u0026#34;lib/*\u0026#34; org.mozilla.javascript.tools.jsc.Main -nosource -d out doDecrypt.js The code inside the Java class file cannot be viewed, even with a Java JAR de-compiler, so I therefore know that my code is safe from prying eyes.\n","date":"19 July 2024","externalUrl":null,"permalink":"/series/ecmascript/encryption/","section":"Series","summary":"Encryption and Decryption # Generate the keys # openssl genrsa -out mykey.pem 2048 openssl rsa -in mykey.pem -pubout \u003e public_key.pem openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in mykey.pem -out private_key.pem doEncrypt # importPackage(java.io); importPackage(java.util); importClass(java.util.Base64); importClass(java.security.cert.CertificateFactory); importClass(java.security.cert.X509Certificate); importClass(java.security.KeyStore); importClass(java.security.spec.X509EncodedKeySpec); importPackage(java.security); importPackage(java.security.interfaces); importPackage(java.security.spec); importPackage(Packages.javax.crypto); importPackage(Packages.javax.crypto.interfaces); importPackage(Packages.javax.crypto.spec); importPackage(Packages.javax.security.cert); var JString = java.lang.String; function doEncrypt ( password, file ) { try { var varCheck = (checkRegex ( password, \"^.+$\") ); myFile = new File (file) key = new JString(Files.readAllBytes(myFile.toPath())); } catch (e) { return ( new JString() ); } publicKey = key.replace(\"-----BEGIN PUBLIC KEY-----\", \"\").replaceAll(System.lineSeparator(), \"\").replace(\"-----END PUBLIC KEY-----\", \"\"); encoded = Base64.getDecoder().decode(publicKey); keyfactory = KeyFactory.getInstance(\"RSA\"); keySpec = new X509EncodedKeySpec(encoded); publicKey = keyfactory.generatePublic(keySpec); encrypt_cipher = Packages.javax.crypto.Cipher.getInstance(\"RSA\"); encrypt_cipher.init(Packages.javax.crypto.Cipher.ENCRYPT_MODE, publicKey); ciphertext = encrypt_cipher.doFinal(new JString(password).getBytes()); var encoded = Base64.getEncoder().encodeToString(ciphertext).getBytes(); return ( new JString(encoded) ); } var varCerts = new Array(); varCerts.push ( \"./public_key.pem\" ); varCerts.push ( \"./private_key.pem\" ); var varPassword = \"MyPassword\"; varEncrypted = doEncrypt ( varPassword, varCerts[0] ); doDecrypt # importPackage(java.io); importPackage(java.util); importClass(java.util.Base64); importClass(java.security.cert.CertificateFactory); importClass(java.security.cert.X509Certificate); importClass(java.security.KeyStore); importClass(java.security.spec.X509EncodedKeySpec); importPackage(java.security); importPackage(java.security.interfaces); importPackage(java.security.spec); importPackage(Packages.javax.crypto); importPackage(Packages.javax.crypto.interfaces); importPackage(Packages.javax.crypto.spec); importPackage(Packages.javax.security.cert); var JString = java.lang.String; function doDecrypt ( password, file ) { try { myFile = new File (file); key = new JString(Files.readAllBytes(myFile.toPath())); } catch (e) { return ( new JString() ); } privateKey = key.replace(\"-----BEGIN PRIVATE KEY-----\", \"\").replaceAll(System.lineSeparator(), \"\").replace(\"-----END PRIVATE KEY-----\", \"\"); encoded = Base64.getDecoder().decode(privateKey); keyfactory = KeyFactory.getInstance(\"RSA\"); keySpec = new PKCS8EncodedKeySpec(encoded); privateKey = keyfactory.generatePrivate(keySpec); decrypt_cipher = Packages.javax.crypto.Cipher.getInstance(\"RSA\"); decrypt_cipher.init(Packages.javax.crypto.Cipher.DECRYPT_MODE, privateKey); try { decoded = Base64.getDecoder().decode(password); ciphertext = decrypt_cipher.doFinal(decoded); return ( new JString(ciphertext) ); } catch (e) { return ( new JString() ); } } var varCerts = new Array(); varCerts.push ( \"./public_key.pem\" ); varCerts.push ( \"./private_key.pem\" ); var varPassword = \"Ou0b5H7ha/sVfqK+gA2hCPhs7c8qeGSTKkMAcyCH2vSjb8CfvfeE8d+NbDwzveYPxC4Rh5GNR0CiQaAZPIiIqV4DMv/i2ELhcjPeI9VWloX5OMrMa5VVoyWElRh93N+D1XP7j+N9ogxRstObLQS1C+uczFmLDEIkWvwNdEM8O7DdkKvgaULiw4r4kfV3ROM0Uu44hxsFTp8V3pIR9ncrkK27ZEE4SGsd1BtimxACNgzqc3fGXxICU4VRDgP0oSSHO35d8ouHt8raAmdVWfKFYqaIWgP9/RSmooHTCaQBfb1tCCLxXrEkEZa47bTIXyIs2I4pBc02OGrDdNMQtAbUWA==\"; varDecrypted = doDecrypt ( varPassword, varCerts[1] ); As an example, I have wrapped the doEncrypt and doDecrypt functions in a separate class. Notice that the two encrypted values are different, due to the fact that the values to encrypt are encrypted using a Public Key.\n","title":"Encryption and decryption","type":"series"},{"content":"The first step is to clone the Void Packages git repository, using the command below. You might want to visit the git repository before you start, as it has important information which you would do well to read first. git repository.\ngit clone https://github.com/void-linux/void-packages.git After running the command shown above, a new void-packages directory will be created. Go ahead and cd in to that directory and run the command shown below. This will configure the build environment.\n./xbps-src binary-bootstrap Next, you\u0026rsquo;ll need to allow restricted packages, so run the following command.\necho XBPS_ALLOW_RESTRICTED=yes \u0026gt;\u0026gt; etc/conf Go ahead and compile a package. In this example I wanted to install the devilspie2 package, so I built it by running the command shown below.\n./xbps-src pkg devilspie2 Once the package is compiled, and all the dependencies have been installed, you can install the package.\nsudo xbps-install --repository=hostdir/binpkgs devilspie2 That\u0026rsquo;s really all there is to it. If you ever come across a package that is not available in the standard Void Linux repositories, checking the Void source packages repository might well have the package you want. If it does, just like the AUR, you can compile it and install it.\n","date":"2 March 2024","externalUrl":null,"permalink":"/posts/linux/void/void-src/","section":"Posts","summary":"The first step is to clone the Void Packages git repository, using the command below. You might want to visit the git repository before you start, as it has important information which you would do well to read first. git repository.\n","title":"Install void src packages","type":"posts"},{"content":"When you export a Provisioning Request Definition (PRD) from NetIQ Designer, you are left with an XML file with 3 separate BASE64 encoded code sections:\nsrvprvProcessXML srvprvRequestXML XMLData Using the custom script decode.js, you can decode these 3 code sections in to 3 separate XML files with the raw code. The decode.js script uses the rhino-1.7.15.jar JavaScript interpreter, so you will need this jar file in your lib directory before you can successfully run the decode.js script. To run the script, put the following command in to a Linux shell script or a Windows batch file: java -cp \"lib/*\" org.mozilla.javascript.tools.shell.Main decode.js \"$@\" To decode the exported PRD in to 3 files, on linux, you can just type: ./decode.sh CAAR_07_02_2024.xml Once the script has finished processing, you will be left with 3 XML files similar to the ones shown below. CAAR_07_02_2024.xml_srvprvProcessXML.xml CAAR_07_02_2024.xml_srvprvRequestXML.xml CAAR_07_02_2024.xml_XMLData.xml Please note that the decodeMe function in the decode.js file has been changed to use the built-in Java Base64 decoder function.The nxsl.jar requirement has been removed and therefore, in addition to replacing the decodeMe function with the code shown below, you should also remove the importClass(Packages.com.novell.xml.util.Base64Codec); code.\nfunction decodeMe ( varString ) { var varDecoded = java.util.Base64.getDecoder().decode(varString); return new java.lang.String(varDecoded); } Download the decode.js script from here.\nDownload the rhino-1.7.15.jar file from here.\nOnce the script has been downloaded, and your lib directory has been setup, create a shell script (similar to the one shown below) in order to run the program.\n#!/bin/bash /Users/belkast/Documents/Applications/JDK/8/arm64/zulu-8.jdk/Contents/Home/bin/java -cp \u0026#34;lib/*\u0026#34; org.mozilla.javascript.tools.shell.Main decode.js \u0026#34;$@\u0026#34; ","date":"9 February 2024","externalUrl":null,"permalink":"/posts/code/idm/workflows/prd-decoder/","section":"Posts","summary":"When you export a Provisioning Request Definition (PRD) from NetIQ Designer, you are left with an XML file with 3 separate BASE64 encoded code sections:\nsrvprvProcessXML srvprvRequestXML XMLData Using the custom script decode.js, you can decode these 3 code sections in to 3 separate XML files with the raw code. The decode.js script uses the rhino-1.7.15.jar JavaScript interpreter, so you will need this jar file in your lib directory before you can successfully run the decode.js script. To run the script, put the following command in to a Linux shell script or a Windows batch file: java -cp \"lib/*\" org.mozilla.javascript.tools.shell.Main decode.js \"$@\" To decode the exported PRD in to 3 files, on linux, you can just type: ./decode.sh CAAR_07_02_2024.xml Once the script has finished processing, you will be left with 3 XML files similar to the ones shown below. CAAR_07_02_2024.xml_srvprvProcessXML.xml CAAR_07_02_2024.xml_srvprvRequestXML.xml CAAR_07_02_2024.xml_XMLData.xml Please note that the decodeMe function in the decode.js file has been changed to use the built-in Java Base64 decoder function.The nxsl.jar requirement has been removed and therefore, in addition to replacing the decodeMe function with the code shown below, you should also remove the importClass(Packages.com.novell.xml.util.Base64Codec); code.\n","title":"Decode PRD content","type":"posts"},{"content":"Instead of being required to open Putty when you want to connect to a SSH host, wouldn\u0026rsquo;t it be rather handy to display the list in Rofi? The script shown below does just that, and it therefore provides a very quick way to connect to the desired SSH host. All you need to change is the config directory containing the putty session files, referenced on line 4.\n#!/bin/bash OLD_IFS=\u0026#34;$IFS\u0026#34; IFS=$\u0026#39;\\n\u0026#39; win_array=($(ls --ignore=\u0026#34;Default*\u0026#34; /home/karmst/.config/putty/sessions)) IFS=\u0026#34;$OLD_IFS\u0026#34; win_length=${#win_array[@]} space=\u0026#34; \u0026#34; for i in \u0026#34;${!win_array[@]}\u0026#34;; do win_array[$i]=${win_array[$i]//\u0026#34;%20\u0026#34;/\u0026#34; \u0026#34;} win_array[$i]=${win_array[$i]//\u0026#34;%28\u0026#34;/\u0026#34;(\u0026#34;} win_array[$i]=${win_array[$i]//\u0026#34;%29\u0026#34;/\u0026#34;)\u0026#34;} win_array[$i]=\u0026#34;$((i+1))\u0026#34;$space${win_array[$i]//\u0026#34;%29\u0026#34;/\u0026#34;)\u0026#34;} done choice=$(printf \u0026#39;%s\\n\u0026#39; \u0026#34;${win_array[@]}\u0026#34; | rofi -dmenu -auto-select -i -p \u0026#34;Putty Session\u0026#34;) \u0026#34;$@\u0026#34; || exit choice1=$(echo $choice | sed \u0026#39;s/\\([0-9]*\\) \\(.\\+\\)$/\\2/\u0026#39;) putty -load \u0026#34;$choice1\u0026#34; exit 0 ","date":"11 January 2024","externalUrl":null,"permalink":"/rofi-putty/","section":"Posts","summary":"Instead of being required to open Putty when you want to connect to a SSH host, wouldn’t it be rather handy to display the list in Rofi? The script shown below does just that, and it therefore provides a very quick way to connect to the desired SSH host. All you need to change is the config directory containing the putty session files, referenced on line 4.\n","title":"Putty sessions in Rofi","type":"posts"},{"content":"Server A is an internet facing server which has port 7000 open to the outside world. Server B is on the internal network, which does not have any ports open to the outside world. To keep things secure, remember to make use of the auth.method which requires that a matching token is set on each of the frp clients. Download frp from github here. frps.toml on server A ( address x.x.x.x ) bindPort = 7000 auth.method = \"token\" auth.token = \"some_random_token\" frpc.toml on server B serverAddr = \"x.x.x.x\" serverPort = 7000 auth.token = \"some_random_token\" [[proxies]] name = \"secret_ssh\" type = \"stcp\" secretKey = \"some_secret_key\" localIP = \"127.0.0.1\" localPort = 22 frpc.toml on client computer serverAddr = \"x.x.x.x\" serverPort = 7000 auth.token = \"some_random_token\" [[visitors]] name = \"secret_ssh_visitor\" type = \"stcp\" serverName = \"secret_ssh\" secretKey = \"some_secret_key\" bindAddr = \"127.0.0.1\" bindPort = 6000 Now you can connect to the local server on port 22, from the client, by using the command below. ssh -oPort=6000 127.0.0.1 ","date":"5 January 2024","externalUrl":null,"permalink":"/frp-tunnel/","section":"Posts","summary":"Server A is an internet facing server which has port 7000 open to the outside world. Server B is on the internal network, which does not have any ports open to the outside world. To keep things secure, remember to make use of the auth.method which requires that a matching token is set on each of the frp clients. Download frp from github here. ","title":"Secure tunnel with frp","type":"posts"},{"content":"","date":"5 January 2024","externalUrl":null,"permalink":"/tags/security/","section":"Tag","summary":"","title":"Security","type":"tags"},{"content":"I own a OnePlus 6t phone which was released in 2018, and the last update the phone officially received was Oxygen OS 11.1.2.2 which was released in December 2021. This updated the phone to Android 11, but Google recently released Android 14. Therefore, to keep the OnePlus phone current with the latest version of Android and additional security patches, I use the crDroid custom Rom. However, Android 14 requires that all applications, at a minimum, must target an Android version greater than 6.0. This is a Google security requirement. The following applications, which I needed to be available on the phone, did not work with the new version of Android. Therefore I had to sideload the applications on to the phone using ADB:\nDashClock Widget v1.7.2.apk DashClock custom extension v2.7.apk Unit Converter v2.8.5.22.apk The command shown below will sideload the my.apk application, thus bypassing the Android 14 minimum SDK level. adb install --bypass-low-target-sdk-block my.apk ","date":"2 January 2024","externalUrl":null,"permalink":"/android-sdk-bypass/","section":"Posts","summary":"I own a OnePlus 6t phone which was released in 2018, and the last update the phone officially received was Oxygen OS 11.1.2.2 which was released in December 2021. This updated the phone to Android 11, but Google recently released Android 14. Therefore, to keep the OnePlus phone current with the latest version of Android and additional security patches, I use the crDroid custom Rom. However, Android 14 requires that all applications, at a minimum, must target an Android version greater than 6.0. This is a Google security requirement. The following applications, which I needed to be available on the phone, did not work with the new version of Android. Therefore I had to sideload the applications on to the phone using ADB:\n","title":"Bypass Android 14 SDK check","type":"posts"},{"content":"The web applet, to be used in a User Application workflow, communicates with NMAS and displays a popup to the User with the result of a password change. The process is interactive, which means that the User does not need to navigate away from the form when the password change is being completed. The User gets the result of the password change instantly, right on the form.\nThe web applet contains code that interprets the NMAS code being returned from NetIQ eDirectory, and displays a human interpretation of the code in the popup dialog that is displayed to the User.\nWorkflow form The image below shows the form that was built in order to facilitate the password change functionality.\nThe password was changed successfully The image below shows a successful password change.\nThe User entered a duplicate password The image below shows a situation where the password was changed to one that had been used previously.\nThe account is disabled The image below shows a situation where the password was changed on an account that was in a disabled state.\n","date":"18 December 2023","externalUrl":null,"permalink":"/password-reset/","section":"Posts","summary":"The web applet, to be used in a User Application workflow, communicates with NMAS and displays a popup to the User with the result of a password change. The process is interactive, which means that the User does not need to navigate away from the form when the password change is being completed. The User gets the result of the password change instantly, right on the form.\n","title":"Password reset web applet","type":"posts"},{"content":"","date":"18 December 2023","externalUrl":null,"permalink":"/tags/workflows/","section":"Tag","summary":"","title":"Workflows","type":"tags"},{"content":"We specialize in a variety of scripting and programming languages, including ECMAScript, Bash scripting for automation and system administration tasks, and Pentaho for data integration and business analytics.\nWe started leveraging the Rhino JavaScript interpreter back in 2008 when we were on a project with Union Pacific. Of course, at that time, there were other ways of writing scripts ( perl and python come to mind ). We, however, found the power of JavaScript and the fact that we could tap in to the Java libraries, while also bringing an easy single file install on Windows, Mac, and Linux, to be a major selling point. Every script we produce uses this method.\nPlease take some time to check out our ECMAScript Code Repository.\nYou can download the latest release of Rhino here.\n","date":"17 December 2023","externalUrl":null,"permalink":"/scripting/","section":"Posts","summary":"We specialize in a variety of scripting and programming languages, including ECMAScript, Bash scripting for automation and system administration tasks, and Pentaho for data integration and business analytics.\nWe started leveraging the Rhino JavaScript interpreter back in 2008 when we were on a project with Union Pacific. Of course, at that time, there were other ways of writing scripts ( perl and python come to mind ). We, however, found the power of JavaScript and the fact that we could tap in to the Java libraries, while also bringing an easy single file install on Windows, Mac, and Linux, to be a major selling point. Every script we produce uses this method.\n","title":"Custom Scripting","type":"posts"},{"content":"NetIQ Identity Management, owned by OpenText, can automate many of the manual tasks associated with user provisioning and de-provisioning, making it a crucial tool for Belkast’s customers. By streamlining these processes, Belkast can help customers save time and money while improving security. In addition, NetIQ Identity Management can help you automate the process of on-boarding and off-boarding users, making it easier and faster to manage access for your employees. You can quickly and easily revoke access for users who are no longer with your company, ensuring that your data remains safe and secure. This is especially important for sensitive data and systems, as it helps to prevent unauthorized access and data leaks. With NetIQ Identity Management, you can quickly and easily provision and de-provision users, as well as set up access controls and permissions. Therefore you can be confident that your data and systems are safe and secure.\n","date":"8 December 2023","externalUrl":null,"permalink":"/identity-management/","section":"Posts","summary":"NetIQ Identity Management, owned by OpenText, can automate many of the manual tasks associated with user provisioning and de-provisioning, making it a crucial tool for Belkast’s customers. By streamlining these processes, Belkast can help customers save time and money while improving security. In addition, NetIQ Identity Management can help you automate the process of on-boarding and off-boarding users, making it easier and faster to manage access for your employees. You can quickly and easily revoke access for users who are no longer with your company, ensuring that your data remains safe and secure. This is especially important for sensitive data and systems, as it helps to prevent unauthorized access and data leaks. With NetIQ Identity Management, you can quickly and easily provision and de-provision users, as well as set up access controls and permissions. Therefore you can be confident that your data and systems are safe and secure.\n","title":"NetIQ Identity Management","type":"posts"},{"content":"","date":"12 November 2023","externalUrl":null,"permalink":"/tags/connectors/","section":"Tag","summary":"","title":"Connectors","type":"tags"},{"content":"It is possible to get one Connector to communicate with another Connector, thereby transferring data from one Connector to another. An example use case might be that you want to query if a User already exists in an Active Directory domain before assigning a unique User ID in the Identity Vault. Or you might want to check one Active Directory domain against another Active Directory domain.\nThe communication uses the DXCMD command line binary; there is already an IDM function to allow you to do this. It is quite easy to do, and uses the following syntax:\n\u0026lt;do-set-local-variable name=\u0026#34;varQueryResult\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-node-set\u0026gt; \u0026lt;token-xpath expression=\u0026#34;dircmd:sendDriverCommand(\u0026#39;~gcvUser-Creation-uidGeneration-Query-Connector~\u0026#39;,$varQueryDocument/nds)\u0026#34;/\u0026gt; \u0026lt;/arg-node-set\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; The Connector on which the Query much be executed is defined in the Global Configuration Value (GCV) shown below.\n\u0026lt;definition display-name=\u0026#34;IDAM Connector to use for Querying Active Directory for \u0026#39;unique Identifier\u0026#39;\u0026#34; name=\u0026#34;gcvUser-Creation-uidGeneration-Query-Connector\u0026#34; type=\u0026#34;string\u0026#34;\u0026gt; \u0026lt;description/\u0026gt; \u0026lt;value\u0026gt;\\IDVAULT\\System\\Identity Management\\IDVAULT\\RDC AD\u0026lt;/value\u0026gt; \u0026lt;/definition\u0026gt; Here is the Query document which is to be sent to the Active Directory Connector.\n\u0026lt;do-set-local-variable name=\u0026#34;varQueryDocument\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-node-set\u0026gt; \u0026lt;token-xml-parse\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;\u0026lt;nds dtdversion=\u0026#34;3.5\u0026#34; ndsversion=\u0026#34;8.x\u0026#34;\u0026gt;\u0026lt;source\u0026gt;\u0026lt;product version=\u0026#34;3.5.11.20080307 \u0026#34;\u0026gt;DirXML\u0026lt;/product\u0026gt;\u0026lt;contact\u0026gt;Novell, Inc.\u0026lt;/contact\u0026gt;\u0026lt;/source\u0026gt;\u0026lt;input\u0026gt;\u0026lt;query class-name=\u0026#34;~gcvUser-Creation-uidGeneration-Query-ClassName~\u0026#34; initiator=\u0026#34;~dirxml.auto.driverdn~\u0026#34; dest-dn=\u0026#34;~gcvUser-Creation-uidGeneration-Query-SearchBase~\u0026#34; scope=\u0026#34;subtree\u0026#34;\u0026gt;\u0026lt;search-class class-name=\u0026#34;~gcvUser-Creation-uidGeneration-Query-ClassName~\u0026#34;/\u0026gt;\u0026lt;search-attr attr-name=\u0026#34;~gcvUser-Creation-uidGeneration-Query-AttributeName~\u0026#34;\u0026gt;\u0026lt;value\u0026gt;$varGeneratedName$\u0026lt;/value\u0026gt;\u0026lt;/search-attr\u0026gt;\u0026lt;read-attr attr-name=\u0026#34;~gcvUser-Creation-uidGeneration-Query-AttributeName~\u0026#34;/\u0026gt;\u0026lt;/query\u0026gt;\u0026lt;/input\u0026gt;\u0026lt;/nds\u0026gt;\u0026lt;/token-text\u0026gt; \u0026lt;/token-xml-parse\u0026gt; \u0026lt;/arg-node-set\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; The dircmd namespace is defined at the top of the policy as:\n\u0026lt;policy xmlns:dircmd=\u0026#34;http://www.novell.com/nxsl/java/com.novell.nds.dirxml.driver.cmd.DriverCmd\u0026#34;\u0026gt; ","date":"12 November 2023","externalUrl":null,"permalink":"/posts/code/idm/idm-driver-comms/","section":"Posts","summary":"It is possible to get one Connector to communicate with another Connector, thereby transferring data from one Connector to another. An example use case might be that you want to query if a User already exists in an Active Directory domain before assigning a unique User ID in the Identity Vault. Or you might want to check one Active Directory domain against another Active Directory domain.\n","title":"Leverage DXCMD","type":"posts"},{"content":"This function can be called from DirXML Script, and offers a reliable way to evaluate XPATH expressions from within the context of the current XDS document.\n\u0026lt;do-set-local-variable name=\u0026#34;varValue\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-node-set\u0026gt; \u0026lt;token-xpath expression=\u0026#34;es:getXPATH( $varDoc, $varXPATH )\u0026#34;/\u0026gt; \u0026lt;/arg-node-set\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; function getXPATH ( varDoc, varExpression ) { var varLevel = 3; tracer.trace(\u0026#34; getXPATH ( doc ) =====\u0026gt;\u0026gt; \u0026#34; + varDoc, varLevel); tracer.trace(\u0026#34; getXPATH ( expression ) =====\u0026gt;\u0026gt; \u0026#34; + varExpression, varLevel); var nodeSet = new Packages.com.novell.xml.xpath.NodeSet(); var document = Packages.com.novell.xml.dom.DocumentFactory.newDocument(); var ndsElement = document.createElement(\u0026#34;nds\u0026#34;); document.appendChild(ndsElement); ndsElement.setAttributeNS(null, \u0026#34;dtdversion\u0026#34;, \u0026#34;3.5\u0026#34;); var outputElement = document.createElement(\u0026#34;output\u0026#34;); ndsElement.appendChild(outputElement); var instanceElement = document.createElement(\u0026#34;instance\u0026#34;); try { var docFactory = DocumentBuilderFactory.newInstance(); var docBuilder = docFactory.newDocumentBuilder(); var xPathFactory = XPathFactory.newInstance().newXPath(); var thisDoc = docBuilder.parse( new org.xml.sax.InputSource( new StringReader( varDoc ) ) ); var thisDocRoot = thisDoc.getDocumentElement(); var thisExpression = xPathFactory.compile( varExpression ); var thisValue = xPathFactory.compile( \u0026#34;.\u0026#34; ); var thisResult = thisExpression.evaluate( thisDocRoot, XPathConstants.NODESET ); var varResult_Length = thisResult.getLength(); var attrElement = document.createElement(\u0026#34;attr\u0026#34;); for ( len = 0; len \u0026lt; varResult_Length; len++ ) { singleNode = thisResult.item( len ).cloneNode(true); var myValue = thisValue.evaluate(singleNode, XPathConstants.STRING); tracer.trace(\u0026#34; getXPATH ( value \u0026#34; + Number(len + 1) + \u0026#34; ) =====\u0026gt;\u0026gt; \u0026#34; + myValue, varLevel); var valueElement = document.createElement(\u0026#34;value\u0026#34;); valueElement.appendChild(document.createTextNode(myValue)); attrElement.appendChild(valueElement); } } catch (err) { var attrElement = document.createElement(\u0026#34;attr\u0026#34;); var myValue = thisExpression.evaluate( thisDocRoot, XPathConstants.STRING ); tracer.trace(\u0026#34; getXPATH ( value 1 ) =====\u0026gt;\u0026gt; \u0026#34; + myValue, varLevel); var valueElement = document.createElement(\u0026#34;value\u0026#34;); valueElement.appendChild(document.createTextNode(myValue)); attrElement.appendChild(valueElement); } finally { instanceElement.appendChild(attrElement); outputElement.appendChild(instanceElement); nodeSet.add(instanceElement); return nodeSet; } } ","date":"5 September 2023","externalUrl":null,"permalink":"/posts/code/get_xpath/","section":"Posts","summary":"This function can be called from DirXML Script, and offers a reliable way to evaluate XPATH expressions from within the context of the current XDS document.\n\u003cdo-set-local-variable name=\"varValue\" scope=\"policy\"\u003e \u003carg-node-set\u003e \u003ctoken-xpath expression=\"es:getXPATH( $varDoc, $varXPATH )\"/\u003e \u003c/arg-node-set\u003e \u003c/do-set-local-variable\u003e function getXPATH ( varDoc, varExpression ) { var varLevel = 3; tracer.trace(\" getXPATH ( doc ) =====\u003e\u003e \" + varDoc, varLevel); tracer.trace(\" getXPATH ( expression ) =====\u003e\u003e \" + varExpression, varLevel); var nodeSet = new Packages.com.novell.xml.xpath.NodeSet(); var document = Packages.com.novell.xml.dom.DocumentFactory.newDocument(); var ndsElement = document.createElement(\"nds\"); document.appendChild(ndsElement); ndsElement.setAttributeNS(null, \"dtdversion\", \"3.5\"); var outputElement = document.createElement(\"output\"); ndsElement.appendChild(outputElement); var instanceElement = document.createElement(\"instance\"); try { var docFactory = DocumentBuilderFactory.newInstance(); var docBuilder = docFactory.newDocumentBuilder(); var xPathFactory = XPathFactory.newInstance().newXPath(); var thisDoc = docBuilder.parse( new org.xml.sax.InputSource( new StringReader( varDoc ) ) ); var thisDocRoot = thisDoc.getDocumentElement(); var thisExpression = xPathFactory.compile( varExpression ); var thisValue = xPathFactory.compile( \".\" ); var thisResult = thisExpression.evaluate( thisDocRoot, XPathConstants.NODESET ); var varResult_Length = thisResult.getLength(); var attrElement = document.createElement(\"attr\"); for ( len = 0; len \u003c varResult_Length; len++ ) { singleNode = thisResult.item( len ).cloneNode(true); var myValue = thisValue.evaluate(singleNode, XPathConstants.STRING); tracer.trace(\" getXPATH ( value \" + Number(len + 1) + \" ) =====\u003e\u003e \" + myValue, varLevel); var valueElement = document.createElement(\"value\"); valueElement.appendChild(document.createTextNode(myValue)); attrElement.appendChild(valueElement); } } catch (err) { var attrElement = document.createElement(\"attr\"); var myValue = thisExpression.evaluate( thisDocRoot, XPathConstants.STRING ); tracer.trace(\" getXPATH ( value 1 ) =====\u003e\u003e \" + myValue, varLevel); var valueElement = document.createElement(\"value\"); valueElement.appendChild(document.createTextNode(myValue)); attrElement.appendChild(valueElement); } finally { instanceElement.appendChild(attrElement); outputElement.appendChild(instanceElement); nodeSet.add(instanceElement); return nodeSet; } }","title":"Evaluate XPATH","type":"posts"},{"content":"The Belkast website was recently migrated from WikiJS to Hugo. Hugo is a static website generator, which is traditionally faster than a website dependent on a database.\nIn addition to migrating the website to Hugo, the content update and website generation process has been simplified:\nThe website content is stored in NextCloud, the self-hosted Belkast cloud backup provider Any change to the website content is automatically uploaded to NextCloud via the NextCloud client or WebDAV A build script is executed via a cron job that runs every thirty minutes, pulling in the website content from NextCloud using rsync The website is automatically generated using hugo This website was generated using Hugo 0.161.1 releases ","date":"14 August 2023","externalUrl":null,"permalink":"/hugo-migration/","section":"Posts","summary":"Information about the Belkast Website","title":"Website migrated to Hugo","type":"posts"},{"content":"Belkast Consulting has worked with many great clients since 2001. The first client we worked with was Capital One Bank in Richmond Virginia. We successfully implemented a JDBC Connector and had a great time delivering the project to the client.\nOne of the longest projects Belkast has been involved with was Union Pacific Railroad. At Allied Irish Banks we were called upon multiple times for different projects, each with varying complexity.\nThe list below gives you an idea of the many different clients that we have worked with over the years.\nA.G. Edwards and Sons Market : Financials City : St. Louis, Missouri Country : United States of America Allied Irish Banks PLC Market : Financials City : Dublin Country : Ireland Biogen Market : Pharmaceuticals City : San Diego, California Country : United States of America Bouvet ASA ( Norway ) Market : Consulting City : Remote Country : Norway California Department of Transportation Market : Transportation City : Sacramento, California Country : United States of America Capital One Bank Market : Financials City : Richmond, Virginia Country : United States of America Catholic Healthcare West Market : Health Care City : Phoenix, Arizona Country : United States of America Corrections Corporation of America Market : Prison System City : Nashville, Tennessee Country : United States of America Environmental Protection Agency Market : Federal Government City : Washington DC Country : United States of America European Broadcasting Union Market : Media / Television City : Geneva Country : Switzerland Humana Market : Health Care City : Louisville, Kentucky Country : United States of America Huntingdon Bank Market : Financials City : Columbus, Ohio Country : United States of America ING Insurance Market : Financials City : Amsterdam / The Hague Country : The Netherlands Kinetic Concepts Market : Health Care City : San Antonio, Texas Country : United States of America Laboratory Corporation of America Holdings Market : Health Care City : Burlington, North Carolina Country : United States of America Metropolitan Transportation Authority Market : Transportation City : New York City, New York Country : United States of America Monsanto Market : Healthcare City : St. Louis, Missouri Country : United States of America Nationwide Insurance Market : Financials City : Columbus, Ohio Country : United States of America Nasdaq Market : Financials City : New York City, New York Country : United States of America Novell ( United Kingdom ) Market : Software City : Remote Country : United Kingdom Novell ( U.S.A. ) Market : Software City : Remote Country : United States of America Palm Beach County Market : Local Government City : West Palm Beach, Florida Country : United States of America Scottish Widows Market : Financials City : Edinburgh Country : United Kingdom State of Indiana Market : State Government City : Indianapolis, Indiana Country : United States of America State of Nevada Market : State Government City : Carson City, Nevada Country : United States of America Surrey County Council Market : Local Government City : Kingston Upon Thames Country : United Kingdom Thrivent Financial Market : Financials City : Minneapolis, Minnesota Country : United States of America TD BankNorth Market : Financials City : Lewiston, Maine Country : United States of America UMB Bank Market : Financials City : Kansas City, Missouri Country : United States of America Union Pacific Railroad Market : Transportation City : Omaha, Nebraska Country : United States of America ","date":"3 August 2023","externalUrl":null,"permalink":"/clients/","section":"Posts","summary":"Belkast Consulting has worked with many great clients since 2001. The first client we worked with was Capital One Bank in Richmond Virginia. We successfully implemented a JDBC Connector and had a great time delivering the project to the client.\n","title":"Client List","type":"dummy"},{"content":"Manjaro Linux has been the default Linux install on the Belkast laptops and, over the last few years, it has been a very stable operating system. It has very rarely had an issue, with OS and package updates always working as intended. Many packages have been installed via the AUR (Arch User Repository), and these have worked well too.\nRecently, however, the number of Manjaro packages installed from the AUR was getting unwieldy, and I am never really 100% comfortable installing packages that are not fully supported by the OS maintainer.\nOver the last few weeks I have been watching YouTube videos about VOID Linux, and it got me thinking. Could I also switch to VOID Linux?\nVOID Linux uses runit for managing system services and, after watching all those videos, it certainly appears that it is a system that is easier to understand. Additionally, because systemd is not used, VOID Linux appears to use less system resources. VOID Linux would be perfect for my Acer Chromebook, I thought, and therefore I started with that laptop. Described below is my experience so far.\nPrepare to Install\nI downloaded the x64 glibc base ISO image from the VOID website. Once the ISO had finished downloading, I wrote the image to a USB stick, which I then proceeded to boot my laptop from.\nAt the initial command prompt, I entered a username of root and a password of voidlinux.\nInstallation\nThe installation is ncurses based and, except for the partitioning section, it is easy to understand and follow. To run the installer, I entered void-installer at the command prompt, and pressed enter. Partitioning\nIf you decide to do a UEFI install, make sure the efi partition has at least 256MB. This will be mounted at /boot/efi. I made the boot partition 1GB on my Dell 7490 so it can hold a few kernel revisions. The partition on my Chromebook is a bit small; my mistake!\nThe partition scheme I chose was the GUID Partition Table GPT. The only thing you need to ensure, when using this partition table type, is that the first partition has the bios_grub flag.\nI decided to set the size of the /boot partition to 350 MB, as this partition will hold the system Linux kernels. A size of 350 MB ensures that three kernels can be accommodated comfortably.\nThe configuration for the first two VOID Linux partitions in a GPT scheme are shown in the table below.\nSize Filesystem Flag Mount 5 MB BIOS boot or ef02 bios_grub None 350 MB vfat None /boot Setup\nChange default repository\nThe VOID Linux repositories contain the majority of packages that you would ever want (or need) to install. For everything else, there is flatpak. One of the first things I did was to change the location of the default repository, since the repository that is configured during installation is located in Germany. Therefore I changed the repository to mirror.clarkson.edu/voidlinux/current/, which is located in the United States.\nAdd three additional repositories:\nvoid-repo-multilib-6_4 void-repo-multilib-nonfree-6_4 void-repo-nonfree-9_6 System Services\nThe first thing I did was to install the system services that I would need. On the Acer laptop, I found that wpa_supplicant was not stable at all. I therefore installed iwd, which proved to be very stable. I removed wpa_supplicant and configured iwd to start at system boot using sudo ln -s /etc/sv/iwd /var/services/. On a MacBook, wpa_supplicant worked very well, so this configuration step was not required.\nI ran in to an issue with the NetworkManager service on a Dell 7490, so make sure the default services match those starting in /var/service.\nService Name: cronie Linked Location: /etc/sv/cronie/ Package to Install: cronie Service Name: elogind Linked Location: /etc/sv/elogind/ Package to Install: elogind Service Name: iwd Linked Location: /etc/sv/iwd/ Package to Install: iwd Service Name: lxdm Linked Location: /etc/sv/lxdm/ Package to Install: lxdm Service Name: netmount Linked Location: /etc/sv/netmount/ Package to Install: sv-netmount Service Name: ntpd Linked Location: /etc/sv/ntpd/ Package to Install: ntp pipewire\nUpdated Information\nPackages\nThe list below shows some of the additional packages that I installed on the VOID Linux system. Of course not everything is available, Proton Bridge is one application that I could not install, but I think I can do without reading email on every single laptop that I own (in addition to my phone).\nName: xorg xorg-server xorg-server-xwayland xorg-video-drivers xorg-input-drivers xinit xauth xrandr xrdb xwininfo xdpyinfo xsetroot neofetch Repository: VOID Type: Core Tools Name: nano, curl, wget, zip, unzip, gdu, plocate, tlp Repository: VOID Type: Core Tools Name: sv-netmount ntp Repository: VOID Description: The sv-netmount application allows you to install the netmount service which enables you to mount network filesystems on system boot, and I use this to mount two samba shares on my WDCloud NAS drive. One I use for music, and one I use for backing up my data. Name: mpd mpv Repository: VOID Description: I use mpd to handle pyradio and local music, with the local music being accessed via a LAN symbolic link in my Music directory. The Acer Chromebook does not have a very powerful GPU, and therefore it cannot handle 4k video downloaded from YouTube. To watch my YouTube subscriptions, I have written a BASH script that uses yt-dlp to stream the video from YouTube. My ~/.config/mpv/mpv.conf file is shown below. ao=pulse ytdl-format=\"bestvideo[height\u003c=?1080]+bestaudio/best\" [pyradio] volume=100 Name: i3-gaps i3lock-color polybar tint2 lxdm elogind Repository: VOID Description: i3-gaps is a Desktop Environment, and lxdm is a display login manager Name: GIMP myPaint xournalpp Repository: VOID Description: Image and PDF processing Name: zsh kitty zsh-autosuggestions zsh-completions starship Repository: VOID Type: Terminal Apps Name: dmenu dunst rofi rofi-calc rofi-emoji rofi-pass Repository: VOID Type: System Tools Name: blezz Repository: Not in a repository Description: An extension for rofi which consists of two files that should be copied in to /usr/lib/rofi/: blezz.la and blezz.so Download:: The two new files are included in the blezz.zip file Name: picom font-awesome jgmenu Repository: VOID Description: Add some bling to your VOID Linux desktop with fancy icons Name: nextcloud-client Repository: VOID Type: I use nextcloud as my cloud backup provider, and the files are stored on a Belkast server hosted by Contabo. Name: LibreOffice Repository: VOID Type: Office Application Name: firefox, qutebrowser Repository: VOID Type: Web Browser Name: librewolf Repository: Flatpak Type: Web Browser Name: Zoom Repository: Flatpak Type: Business Application Name: Enpass Repository: Flatpak Type: Password Manager Other things\nMacBook\nI had to add the following lines to the /etc/rc.local file:\nmodprobe -r usbmouse modprobe -r bcm5974 modprobe bcm5974\nEnpass\nTo use enpass, you need to install it as a flatpak and then edit the flatpak config file to allow access to the local filesystem; otherwise it will not save any of the configured vaults.\n[Desktop Entry] Version=1.0 Type=Application Name=Enpass GenericName=Enpass Password manager Icon=io.enpass.Enpass Terminal=false Exec=/usr/bin/flatpak run --branch=stable --arch=x86_64 --filesystem=host --command=enpass --file-forwarding io.enpass.Enpass @@u %U @@ MimeType=x-scheme-handler/enpassauth;x-scheme-handler/enpasscard;x-scheme-handler/enpassstart;x-scheme-handler/enpass;x-scheme-handler/cloudkit-7adb8cc6tf.in.sinew.walletx;application/enpasscard; Categories=Utility X-Flatpak-Tags=proprietary; X-Flatpak=io.enpass.Enpass runit\nMake sure the configured services are also in the default run level. The directory to check is /etc/runit/runsvdir/default.\nacpid ⇒ /etc/sv/acpid agetty-tty1 ⇒ /etc/sv/agetty-tty1 agetty-tty2 ⇒ /etc/sv/agetty-tty2 agetty-tty3 ⇒ /etc/sv/agetty-tty3 agetty-tty4 ⇒ /etc/sv/agetty-tty4 agetty-tty5 ⇒ /etc/sv/agetty-tty5 agetty-tty6 ⇒ /etc/sv/agetty-tty6 bluetoothd -\u0026gt; /etc/sv/bluetoothd cronie ⇒ /etc/sv/cronie/ dbus ⇒ /etc/sv/dbus elogind ⇒ /etc/sv/elogind/ iwd ⇒ /etc/sv/iwd libvirtd ⇒ /etc/sv/libvirtd lxdm ⇒ /etc/sv/lxdm/ mpd ⇒ /etc/sv/mpd netmount ⇒ /etc/sv/netmount/ NetworkManager ⇒ /etc/sv/NetworkManager ntpd ⇒ /etc/sv/ntpd polkitd ⇒ /etc/sv/polkitd sshd ⇒ /etc/sv/sshd tailscaled ⇒ /etc/sv/tailscaled tlp ⇒ /etc/sv/tlp/ udevd ⇒ /etc/sv/udevd virtlockd ⇒ /etc/sv/virtlockd virtlogd ⇒ /etc/sv/virtlogd Virtual Machines\nsudo usermod -a -G libvirt,kvm karmst\nInstalled Packages List\nAfter installing the base VOID OS, I installed the following packages.\narc-theme-20221218_1 bat-0.25.0_1 blueman-2.4.3_2 bluez-5.78_1 brightnessctl-0.5.1_1 castero-0.9.5_3 chrony-4.5_2 cifs-utils-6.15_1 clipmenu-6.2.0_1 cronie-1.7.2_1 cryptsetup-2.7.5_2 curl-8.9.1_1 dejavu-fonts-ttf-2.37_2 dmenu-5.3_1 dunst-1.11.0_1 elogind-252.9_2 entr-5.7_1 exa-0.19.1_1 eza-0.19.1_1 fastfetch-2.37.0_1 feh-3.10.3_1 flatpak-1.15.10_1 floorp focuswriter-1.8.10_1 font-awesome-4.7.0_3 font-awesome5-5.15.4_2 font-awesome6-6.7.1_1 font-hack-ttf-3.003_2 font-weather-icons-2.0.10_2 fonts-roboto-ttf-2.138_3 fwupd-2.0.3_1 fzf-0.60.2_1 gdu-5.29.0_2 git-2.48.1_1 gnome-disk-utility-46.1_1 gnome-keyring-46.2_1 gnupg-2.4.5_1 go-1.25.0_1 gocryptfs-2.4.0_2 gpick-0.3_1 htop-3.3.0_1 imagewriter-1.10.20150521_2 i3-4.23_1 i3-gaps-4.23_1 i3lock-color-2.13.c.4_1 i3status-2.15_1 intellij-idea-community-edition-2024.3.1.1_1 iwd-2.19_1 jgmenu-4.4.1_1 jq-1.7.1_1 kitty-0.36.1_1 kvantum-1.0.10_1 less-643_1 libreoffice-24.2.5.1_1 lsd-1.1.5_2 lxdm-0.5.3_5 maim-5.7.4_7 mc-4.8.31_1 meld-3.22.3_1 micro-2.0.14_1 mlocate-0.26_7 mpc-0.35_1 mpd-0.23.15_3 mpv-0.38.0_3 mupdf-1.24.9_1 nano-8.1_1 ncmpcpp-0.9.2_11 nerd-fonts-ttf-3.2.1_1 netcat-0.7.1_7 network-manager-applet-1.36.0_1 NetworkManager-1.48.8_1 newsboat-2.36_1 nextcloud-client-3.13.1_1 noto-fonts-emoji-2.042_1 noto-fonts-ttf-24.8.1_2 noto-fonts-ttf-extra-24.8.1_2 ntfs-3g-2022.10.3_1 ntp-4.2.8p15_7 openjdk11-11.0.25+5_1 openvpn-2.6.12_2 pamixer-1.6_2 papirus-icon-theme-20240501_1 pass-1.7.4_3 pass-otp-1.2.0_2 pavucontrol-6.1_1 picom-11.2_1 pinentry-1.3.1_1 pinentry-dmenu-0.2.3_1 pinentry-gtk-1.3.1_1 pipewire-1.2.2_1 plocate-1.1.22_1 polkit-124_1 polkit-qt5-0.200.0_1 polybar-3.7.2_1 poppler-utils-25.02.0_2 protonmail-bridge-3.12.0_2 protonvpn-cli-2.2.12_1 pulseaudio-16.1_2 pulseaudio-utils-16.1_2 putty-0.81_1 pyradio-0.9.3.9_1 qalculate-5.2.0_1 qtpass-1.3.2_1 qpdf-11.6.4_1 qt5ct-1.7_1 qemu-9.0.2_1 qutebrowser-3.2.1_1 rclone-1.67.0_2 ripgrep-14.1.1_1 rofi-1.7.5_1 rofi-calc-2.2.1_1 rofi-emoji-3.3.0_1 rofi-pass-2.0.2_1 rsync-3.3.0_1 screenkey-1.5_3 sof-firmware-2025.01_1 source-sans-pro-3.046_1 starship-1.20.1_1 sublime-text4-4189_1 sv-netmount-0.1_3 sxhkd-0.6.2_1 tailscale-1.72.1_1 telegram-desktop-5.12.3_1 thunderbird-115.12.2_1 tilda-1.5.4_1 tint2-17.0.2_1 tldr-1.0.0.alpha_5 tlp-1.6.1_1 trash-cli-0.24.5.26_2 ttf-material-icons-4.0.0_2 ttf-opensans-3.001_1 ttf-ubuntu-font-family-0.83_3 ueberzug-18.1.9_4 unicode-emoji-14.0_1 unzip-6.0_15 variety-0.8.12_2 viewnior-1.8_2 vifm-0.13_1 vim-9.1.0772_2 virt-manager-4.1.0_3 void-repo-multilib-6_4 void-repo-multilib-nonfree-6_4 void-repo-nonfree-9_6 vscode-1.91.1_1 wget-1.24.5_1 wireplumber-0.5.5_1 wmctrl-1.07_6 xauth-1.1.3_1 xcolor-0.5.1_2 xclip-0.13_2 xdotool-3.20211022.1_1 xdpyinfo-1.3.4_2 xev-1.2.6_1 xinput-1.6.4_1 xmirror-0.3_1 wmctrl-1.07_6 xmlstarlet-1.6.1_2 xorg-fonts-7.6_5 xorg-input-drivers-7.6_4 xorg-minimal-1.2_2 xorg-video-drivers-7.6_23 xournalpp-1.2.3_1 xrandr-1.5.3_1 xrdb-1.2.2_1 xsetroot-1.1.3_1 xwallpaper-0.7.4_1 xwininfo-1.1.6_1 yad-14.1_2 yewtube-2.10.5_2 yt-dlp-2024.08.06_1 zip-3.0_6 zoxide-0.9.7_1 zsh-5.9_3 zsh-autosuggestions-0.7.0_1 zsh-completions-0.35.0_2 Other Things flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo Copy blezz files from backup Copy warp (wd) from backup Import GPG keys (keys.asc) Make sure pinenty is configured correctly Generate goCryptFS configuration file gtk 3.0 settings.ini file: gtk-icon-theme-name=Papirus Update fstab to include mounting of NAS AppImage package manager wget -q https://raw.githubusercontent.com/ivan-hc/AM/main/AM-INSTALLER \u0026\u0026 chmod a+x ./AM-INSTALLER \u0026\u0026 ./AM-INSTALLER get_iplayer */15 * * * * /home/karmst/scripts/iPlayer/bbc_update.sh \u003e/dev/null 2\u003e\u00261 perl-Mojolicious perl-HTML-Parser perl-HTTP-Cookies perl-LWP perl-XML-LibXML perl-LWP-Protocol-https Themes Add the following to /etc/environment: QT_QPA_PLATFORMTHEME=qt5ct QT_STYLE_OVERRIDE=kvantum SUDO_ASKPASS=/usr/local/bin/dpass /usr/local/bin directory: dmenu dpass get_iplayer ","date":"1 August 2023","externalUrl":null,"permalink":"/posts/linux/void/","section":"Posts","summary":"Manjaro Linux has been the default Linux install on the Belkast laptops and, over the last few years, it has been a very stable operating system. It has very rarely had an issue, with OS and package updates always working as intended. Many packages have been installed via the AUR (Arch User Repository), and these have worked well too.\n","title":"Installing VOID Linux","type":"posts"},{"content":" Upgrades # Is your organization currently relying on outdated or unsupported NetIQ Identity and Access Management software? If so, it may be time to consider an upgrade to ensure the continued security, efficiency, and compliance of your systems.\nWe specialize in assisting organizations like yours in transitioning from a legacy or unsupported NetIQ IDM solution to a modern, robust, and supported platform.\nWe have extensive experience with upgrading the NetIQ IDM software stack, whether it is the IDM engine or, a bit more complicated, the IDM User Application. We can do both.\nAs you probably already know, upgrading the IDM User Application is not for the faint of heart. But do not despair. We can be of assistance. We recently participated in a successful upgrade from 4.0.2 to 4.8.6.\nNetIQ eDirectory upgrades are not a problem, either. We do not have experience with NetIQ Access Manager or NetIQ Sentinel. But we know people who have. So please get in touch.\nMigrations # There may come a time when your organization seeks to explore new software solutions to enhance efficiency, security, or scalability. If you are considering a transition to a different platform or system, we are here to support your software migration needs.\nWe have experience with software migration if you decide you want to move away from NetIQ to another Identity Management vendor.\nWe provide the following documentation services:\nDocument the Publisher channel Document the Subscriber channel Document the Driver Filter Document the Driver Schema Map Document all ECMAScript Functions Document all Driver Configuration Settings Document all Global Configuration Values Document all Named Passwords Document all Mapping Tables Document all Driver Jobs Document the Driver business logic Document all Driver attribute transformations Document the Object creation requirements Document the Object matching requirements Document the Object placement requirements Document all in scope Object attributes Document the Identity Vault structure The resulting document can be used by the newly chosen vendor to replicate your current driver configuration.\n","date":"16 May 2023","externalUrl":null,"permalink":"/upgrades/","section":"Posts","summary":"Upgrades # Is your organization currently relying on outdated or unsupported NetIQ Identity and Access Management software? If so, it may be time to consider an upgrade to ensure the continued security, efficiency, and compliance of your systems.\n","title":"Upgrades and Migrations","type":"posts"},{"content":"Initial Login\nLogin to NetIQ iManager. Navigate to the Connector on which you want to configure the trace. Driver Overview\nEither of the following actions will open the Driver Configuration window: Clicking anywhere on the Connector icon Clicking on the circle in the top right hand corner of the icon, followed by clicking on Edit properties, as shown in the screenshot below Driver Configuration\nIn the window that appears, navigate to the Misc tab and configure the trace as required. Click Apply and then OK to complete the change. The trace file will be created / updated based on the settings, and it will be located in the directory that was specified in the configuration. Trace Levels\nLevel 0 or Level 1 should be the level that is set when running the Connector in a production environment. Level 3 is enough to troubleshoot a Connector issue, and should be the minimum level that is selected when sending the log file to a third party. Level 10 provides as much log detail as you would ever need. To read another one of our recent iManager posts To export Connector code using iManager, click here.\n","date":"25 March 2023","externalUrl":null,"permalink":"/posts/code/idm/config-trace-level/","section":"Posts","summary":"Initial Login\nLogin to NetIQ iManager. Navigate to the Connector on which you want to configure the trace. Driver Overview\nEither of the following actions will open the Driver Configuration window: Clicking anywhere on the Connector icon Clicking on the circle in the top right hand corner of the icon, followed by clicking on Edit properties, as shown in the screenshot below Driver Configuration\n","title":"Configuring Trace Level","type":"posts"},{"content":"Follow the steps listed below to export Connector code using iManager.\nAccess the NetIQ iManager Login Page Navigate to the NetIQ iManager URL, and you should see a screen similar to the one shown below. Login to the NetIQ iManager web interface. Identity Manager Administration tab The initial screen, shown below, will be displayed. Click on the Identity Manager Overview link in the top left hand corner of the initial screen. Driver Set selection A Driver Set search screen will be displayed, and on this screen you will need to select the Driver Set where the Driver you want to export is located. Click on Driver Sets. The list of Driver Sets in the eDirectory Tree are displayed, similar to the screen shown below. Click on the Driver Set which contains the Driver that you would like to export. Driver Overview The Drivers contained in the Driver Set previously selected are displayed. Click on the Driver that you would like to export. The Driver overview is displayed in the right hand pane, as shown in the screen below. Click on Export. Export Configuration Wizard The Export Configuration Wizard window will open.\nClick Next. If you have more than one server in the DriverSet, select the server that holds the Driver Parameters and Global Configuration Values, and then click Next Click Next on the penultimate wizard page, and then click Save As Click Finish To update the trace configuration on a Connector, click here.\n","date":"25 March 2023","externalUrl":null,"permalink":"/posts/code/idm/export-connector/","section":"Posts","summary":"Follow the steps listed below to export Connector code using iManager.\nAccess the NetIQ iManager Login Page Navigate to the NetIQ iManager URL, and you should see a screen similar to the one shown below. Login to the NetIQ iManager web interface. Identity Manager Administration tab The initial screen, shown below, will be displayed. Click on the Identity Manager Overview link in the top left hand corner of the initial screen. Driver Set selection A Driver Set search screen will be displayed, and on this screen you will need to select the Driver Set where the Driver you want to export is located. Click on Driver Sets. The list of Driver Sets in the eDirectory Tree are displayed, similar to the screen shown below. Click on the Driver Set which contains the Driver that you would like to export. Driver Overview The Drivers contained in the Driver Set previously selected are displayed. Click on the Driver that you would like to export. The Driver overview is displayed in the right hand pane, as shown in the screen below. Click on Export. Export Configuration Wizard The Export Configuration Wizard window will open.\n","title":"iManager Connector export","type":"posts"},{"content":"The format for the Novell Query Processor is shown below, along with a description of the parameters and a few examples.\nquery:search($srcQueryProcessor/$destQueryProcessor,\u0026lt;scope\u0026gt;, \u0026lt;association\u0026gt;, \u0026lt;srcDN/destDN\u0026gt;, \u0026lt;className\u0026gt;, \u0026lt;searchAttr\u0026gt;, \u0026lt;searchValue\u0026gt;, \u0026lt;attrs\u0026gt;) The parameters are described below:\nscope – “entry”, “subtree”, or “subordinates” association – association value to use for query srcDN/destDN – src-dn or dest-dn value to use for query – depends on $srcQueryProcessor or $destQueryProcessor className – object class name (empty string to search all classes) searchAttr – attribute to search on (empty string to not limit by attribute value) searchValue – string value to search on for search attr attrs – comma separated list of attributes to read, empty to read no attributes, * to read all attributes \u0026lt;do-set-local-variable name=\u0026#34;varResourceQuery\u0026#34;\u0026gt; \u0026lt;arg-node-set\u0026gt; \u0026lt;token-xpath expression=\u0026#39;query:search($srcQueryProcessor,\u0026#34;subtree\u0026#34;,\u0026#34;\u0026#34;,\u0026#34;~gcvLocation-Entitlement-Groups~\u0026#34;,\u0026#34;\u0026#34;,\u0026#34;~gcvAttribute-Resource-AppAssociation~\u0026#34;,$varEntitlementParamKey,\u0026#34;DirXML-Associations\u0026#34;)\u0026#39;/\u0026gt; \u0026lt;/arg-node-set\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varSearchCurrentDestDN\u0026#34;\u0026gt; \u0026lt;arg-node-set\u0026gt; \u0026lt;token-xpath expression=\u0026#39;query:search($srcQueryProcessor,\u0026#34;entry\u0026#34;,\u0026#34;\u0026#34;,@dest-dn,\u0026#34;User\u0026#34;,\u0026#34;\u0026#34;,\u0026#34;\u0026#34;,\u0026#34;\u0026#34;)/@src-dn\u0026#39;/\u0026gt; \u0026lt;/arg-node-set\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-xml-attr expression=\u0026#34;../modify[last()]\u0026#34; name=\u0026#34;qualified-src-dn\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-xpath expression=\u0026#39;query:search($srcQueryProcessor,\u0026#34;entry\u0026#34;,\u0026#34;\u0026#34;,$varUser-Owner-Removed,\u0026#34;\u0026#34;,\u0026#34;\u0026#34;,\u0026#34;\u0026#34;,\u0026#34;\u0026#34;)/@qualified-src-dn\u0026#39;/\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-xml-attr\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varQueryResult\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-node-set\u0026gt; \u0026lt;token-xpath expression=\u0026#39;query:search($srcQueryProcessor,\u0026#34;subtree\u0026#34;,\u0026#34;\u0026#34;,\u0026#34;~gcvLocation-Root~\u0026#34;,\u0026#34;User\u0026#34;,\u0026#34;uniqueID\u0026#34;,$varGeneratedName,\u0026#34;uniqueID\u0026#34;)\u0026#39;/\u0026gt; \u0026lt;/arg-node-set\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varManager\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-xpath expression=\u0026#34;./operation-data/is-manual-transfer/@transfer-to\u0026#34;/\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varManager-LDAP\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-parse-dn dest-dn-format=\u0026#34;ldap\u0026#34; src-dn-format=\u0026#34;qualified-slash\u0026#34;\u0026gt; \u0026lt;token-xpath expression=\u0026#39;query:search($destQueryProcessor,\u0026#34;entry\u0026#34;,\u0026#34;\u0026#34;,$varManager,\u0026#34;\u0026#34;,\u0026#34;\u0026#34;,\u0026#34;\u0026#34;,\u0026#34;\u0026#34;)/@qualified-src-dn\u0026#39;/\u0026gt; \u0026lt;/token-parse-dn\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; ","date":"11 August 2022","externalUrl":null,"permalink":"/posts/code/idm/idm-srcquery/","section":"Posts","summary":"The format for the Novell Query Processor is shown below, along with a description of the parameters and a few examples.\nquery:search($srcQueryProcessor/$destQueryProcessor,\u003cscope\u003e, \u003cassociation\u003e, \u003csrcDN/destDN\u003e, \u003cclassName\u003e, \u003csearchAttr\u003e, \u003csearchValue\u003e, \u003cattrs\u003e) The parameters are described below:\n","title":"DirXML Query Processor","type":"posts"},{"content":"Belkast Consulting has development a reusable Framework which simplifies the integration of both Connected and Non Connected Systems with respect to NetIQ IDM. By using this Framework, you can implement the same application permission logic regardless of whether the System is connected via an IDM Connector or is completely ‘stand alone’.\nThe differences # A Connected System is a System which sends or receives data directly to or from the Identity Vault. It is a System which is connected to the Identity Vault via an IDM Connector. Identity Vault Objects synchronize with the Connected System; whether it is a User Object, a Group Object, a Computer Object, whatever type of Object. In the Connected System scenario Users would, for instance, be placed in one or more Groups in order to assign permissions for the Connected System. In the case of Active Directory where applications use Groups to determine access, manipulating Group Membership is key in defining application access. Not so with a Non Connected System.\nA Non Connected System is a System which is not connected to the Identity Vault via an IDM Connector; it is said, therefore, to be ‘stand alone’. The Identity Vault, for all intents and purposes, is an island; the Identity Vault has User and Group data, but the Non Connected System never has knowledge of this data. Therefore, what happens in the Identity Vault stays in the Identity Vault.\nWhy a framework is needed # Architecturally speaking, a Framework is required so that all data processes stay identical regardless of whether the process affects a Connected System or a Non Connected System. The access rights for a Connected System are handled in exactly the same way as those for a Non Connected System. The only difference is the method by which the access changes are relayed to the end System. A Connected System is notified via the automatic synchronization of events across the IDM Connector; a Non Connected System (rather the ‘owner’ of the System) is notified via an email alert. However, the data representation in the Identity Vault (i.e. Roles, Resources, and Entitlements) is exactly the same.\nThe following list shows what we attempted to accomplish with the Framework:\nDO NOT over complicate permissions to applications. The permissions are represented by Group Membership Make the data representation for Connected and Non Connected Systems work exactly the same way Use Roles, Resources, and Entitlements – adding an Entitlement puts the User in a Group; removing the Entitlement removes the Group membership DO NOT use Entitlement Queries to external application. Store the Entitlement lookup Objects (Groups) locally in the Identity Vault If you follow the above rules you will have implemented a solution which is supported, extensible, and easy to manage. So, you might ask, how does one go about creating a solution similar to this?\nUsing Entitlements for all permission logic # Regardless of whether the System is Connected or Non Connected, treat permissions to those Systems as Group Membership. Using an Entitlement Query allows the NetIQ User Application (RBPM) to tie a Group to a Resource. Since one or more Resources can be assigned to a Role, assigning a User to a Role could (and should) result in the User being placed in one or more Groups. This is where the Identity Vault logic ends and the Connected System (IDM Connector) logic takes over; either that, or an email is sent to the application ‘owner’ directing them to assign the User the appropriate application permission(s).\nCustom Components # Q: Surely it is easier said than done to just distinguish between a Connected and a Non Connected System? A: Well, yes, this is true. But Belkast Consulting have implemented a Framework to ensure that the definition of permissions to Connected, and Non Connected, Systems is defined in exactly the same way, every time. The following components are present in the Framework. User Application Workflow # A custom built Workflow allows privileged Users to create an application ‘definition’, Connected AND Non Connected. The User defines who the owners of the application are, to which Connector (or not) the application belongs, which Groups should be ‘sucked in’ from the Connected System (or not). Basically, Connected and Non Connected Systems can have multiple applications defined. Each application is defined as an Organizational Unit in the Identity Vault. Each application permission, or set of permissions, is defined as a Group.\nIDM ‘Application Manager’ Connector # This custom built Connector implements the following logic:\nCreation of an Entitlement Object (and update of entitlement.xml) when an Organizational Unit is created The Entitlement Object defines a Query which returns all the Groups under the OU which is defined as the ‘application’ By doing this EXACT SAME LOGIC for both Connected and Non Connected applications, the code works seamlessly for both The value in the Entitlement string put on a User is oblivious to the fact that the Entitlement is for a Connected of a Non Connected application Deletion of an Entitlement Object (and update of entitlement.xml) when an Organizational Unit is deleted When an Organizational Unit is deleted, all Group Membership (the Groups are deleted also) and Resources are revoked It is therefore extremely easy to remove the definition of an application. All the associated data on affected Users is cleaned up automatically Optionally synchronize all application Groups when an Organizational Unit is created. The Groups are placed under the correct OU On assigning an Entitlement to a User, places the User in the appropriate Group On removing an Entitlement from a User, removes the User from the the appropriate Group If an application group is deleted, revokes the appropriate Resource from all Users who were in the Group If a Group is created, and the creation did not originate from a Connected System, the Group is assigned a Unique ID (DirXML-Association value) ECMAScript for Group synchronization # This custom built script, which is shared by all Connected System IDM Connectors, converts the Entitlement Value (DirXML-Association) into a Group DN so each IDM Connector never needs to worry about parsing the value.\nIdentity Vault configuration # Each application is represented in the Identity Vault in the following way:\nAn ‘application’ is simply an Organizational Unit Each application is assigned to a Connector (DN) or tagged as ‘Non Connected’ Each application is assigned one or more ‘owners’ (DN) Each ‘permission’ is a Group. Each application Group is created under the appropriate Organizational Unit During the definition of a ‘Connected’ application, the User defines where in the Connected System the application Groups are located. If applicable, all Groups in the location defined are ‘sucked in’ from the application; the DirXML-Association value on the Group is the uniqueID contained in the Connected System. For Non Connected applications, all Groups created under the applicable Organisational Unit are assigned a DirXML-Association which is uniquely generated by the Unique ID Generator Connector. Click here to see the code which is responsible for synchronising the Connected System Groups.\nHow do the Entitlements work # When the Organisational Unit is created, a Utility Loopback Connector (already built by Belkast) creates the Entitlement definition for that ‘application’. This allows:\nFor the display of all applications from within the User Application For the assignment of a valued entitlement (just a Group) to a Resource For the Resource to be assigned to a Role The value of the Entitlement is ALWAYS the DirXML-Association of the Group; therefore, when a User receives an Entitlement, the Utility Loopback Connector (already built by Belkast) places the User in the appropriate Group. If the Group Object is a Connected System Group (it has been synchronized from a Connected System) then the User will synchronize to that application Group in the Connected System. If the Group Object is a Non Connected System Group, there needs to be logic built to send out an email. However, this is fairly straightforward because the Group Object is located under an Organisational Unit which contains the email address of the application owner (this was done using the Workflow mentioned above). The Organisational Unit is also tagged as a ‘Non Connected System’, so there should be no ambiguity.\nSupported Architecture # This architecture is supported because the way in which the Groups are assigned to the User are based on entitlements (which are supported) and are not based on an ‘if then else’ rule on an application by application basis.\n","date":"19 May 2022","externalUrl":null,"permalink":"/posts/custom/system-framework/","section":"Posts","summary":"Belkast Consulting has development a reusable Framework which simplifies the integration of both Connected and Non Connected Systems with respect to NetIQ IDM. By using this Framework, you can implement the same application permission logic regardless of whether the System is connected via an IDM Connector or is completely ‘stand alone’.\n","title":"Connected System Framework","type":"posts"},{"content":"Belkast Consulting has developed a Java command line utility to assist Micro Focus Identity Management customers feel confident that password synchronization is working.\nFunctionality # Reset a Users password as either the admin or as the User. For compliant password policies in eDirectory, this will either set the password expired or not expired. Supports both SSL and clear text connections. All tasks work on the result of an LDAP Query: It is therefore possible to reset the password on multiple accounts It is also possible to verify password synchronization for multiple accounts at once Supports any LDAP v3 compliant directory Active Directory, eDirectory, Oracle Internet Directory, openLDAP For Active Directory, login verification can use either LDAP DN syntax or DOMAIN\\\\LOGIN syntax The LOGIN value can be retrieved from an attribute on the User object. If no attribute value is defined, the User DN is used For additional security, in the configuration file, the Password and the Username are encrypted using a 16 byte key The configuration file supports multiple [server] sections Properties file # \u0026lsquo;\u0026lsquo;\u0026rsquo;text [program] ERROR_CODES = ./errors.ini\n[main] NAME = CentOS VM eDirectory HOSTNAME = 192.168.174.10 PORT = 636 USERNAME = X8gBJzLauRkDuoHR68Fo/ikCtYBy4fZWm6hhGCbDlCQ= PASSWORD = AHuoo1UkLJUtIYPg8teFjQ== TREE_NAME = IDVAULT LDAP_BASE = o=belkast LDAP_QUERY = (uid=KARMST) LDAP_SCOPE = sub LDAP_TIMEOUT = 10000 SSL = true DEBUG = true CACERTS = ./keith.jks LOG_FILE = ./passwords.log LOG_PASSWORD = true\n[server] TYPE = AD HOSTNAME = 192.168.174.20 PORT = 389 SSL = false LOGIN_ATTR = uid LOGIN_TYPE = domain domain = CORP \u0026rsquo;\u0026rsquo;\u0026rsquo;\nExamples # java -jar ./dist/verifyPassword.jar –props props_GDS.ini –key IanLovesCrackers\n### password changer \u0026amp; sync verifier ###\n### version: 09.10.15.001 ###\n### belkast consulting © 2015 ###\n### email: keith@belkast.com ###\n### Read \\[1\\] server(s) from props.ini ###\n### Connecting to CentOS VM eDirectory\n### Running query: filter \\[(uid=KARMST)\\], scope \\[sub\\], base \\[o=belkast\\] ###\n### Query returned 1 result(s) ###\n@@@ Processing new User Object @@@\n@@@ \\[cn=KARMST,ou=ACTIVE,ou=USERS,o=BELKAST\\] @@@\n### Get pwd for \\[cn=KARMST,ou=ACTIVE,ou=USERS,o=BELKAST\\] ###\n### Got pwd length {10} ###\n### \\[1 of 1\\] Logging in to \\[192.168.174.20:389\\] ###\n### \\[1 of 1\\] Logging in as \\[CORP\\\\KARMST\\] ###\njava -jar ./dist/verifyPassword.jar –encrypt cn=admin,ou=users,ou=admin,o=belkast –key IanLovesCrackers\n### password changer \u0026amp; sync verifier ###\n### version: 09.10.15.001 ###\n### belkast consulting © 2015 ###\n### email: keith@belkast.com ###\n### Key to use: IanLovesCrackers\n### Encrypting value: cn=admin,ou=users,ou=admin,o=belkast\n### Encrypted value: /TjqHUFnIZq6vtRKWa0G4p+Koq/8mjb3ml+7tfE4AWb4/PXy1XDAGQmLXS7yClYp\n### Decrypted value: cn=admin,ou=users,ou=admin,o=belkast\n","date":"8 March 2022","externalUrl":null,"permalink":"/posts/custom/password-setter/","section":"Posts","summary":"Belkast Consulting has developed a Java command line utility to assist Micro Focus Identity Management customers feel confident that password synchronization is working.\nFunctionality # Reset a Users password as either the admin or as the User. For compliant password policies in eDirectory, this will either set the password expired or not expired. Supports both SSL and clear text connections. All tasks work on the result of an LDAP Query: It is therefore possible to reset the password on multiple accounts It is also possible to verify password synchronization for multiple accounts at once Supports any LDAP v3 compliant directory Active Directory, eDirectory, Oracle Internet Directory, openLDAP For Active Directory, login verification can use either LDAP DN syntax or DOMAIN\\\\LOGIN syntax The LOGIN value can be retrieved from an attribute on the User object. If no attribute value is defined, the User DN is used For additional security, in the configuration file, the Password and the Username are encrypted using a 16 byte key The configuration file supports multiple [server] sections Properties file # ‘‘’text [program] ERROR_CODES = ./errors.ini\n","title":"Password Verifier / Setter","type":"posts"},{"content":"Often times you will want to scramble data before you store it in an LDAP directory or a database. The quickest method is to simply obfuscate the data by replacing each character in the original data with different ones, of course by using a known algorithm. For example, shifting the characters over two places on the ASCII scale before replacement. Once this is done, you would then Base64 encode the data. Not the most secure of methods, but it will work and make the data look like something it is not.\nA different method would be to write a piece of ECMAScript code or compile your own Java Class to actually encrypt the data using a known seed. The problem with the ECMAScript method is that the seed and the encryption algorithm is open and could enable a malicious User to figure out the method used; it’s ‘in the clear’ ECMAScript source code after all. Of course, the seed could be stored in a Named Password on the IDM Driver or IDM DriverSet, but this does not provide an uncrackable solution. The Java Class method is one Belkast has used in the past, but this requires yet another JAR file to be placed on the IDM server. When moving code from Development to Test and from Test to Production, things get left behind and this can often lead to broken code. I think everyone has been in that situation before!\nThe method that Belkast prefers when working with data encryption is to use the Public Key from a self-signed certificate. It does involve writing a little bit of ECMAScript code, but what it means is that:\nThe encrypted output of the same value is different every time (just like SSL) The original clear text value cannot be retrieved unless a malicious User has the certificate Private Key As a use case, we encrypted the value Belkast123 (a valid password) two times, and the final encrypted values are shown below. As you can see the results are different, thereby making it more difficult for a malicious User to determine the initial, non-encrypted, value.\niI1t+CmUYQRBiA4FxB01svuS+zTmDpSgabiUghktzQRc3aYNlpILS8Zh+ptoegraDM0avD/AGJNlbbUWxv5oYdjRMAuJA/sm7cHVxiasb9W9Wd8qWCefSVUil/eO+S2rX2kUB949dcLYBK4BuTDY/bs/ah7PYmPi/te+w5cLL1iyvpzjr+acFwRXo/71XLQJ46NYZFdeDShlk8eqOLTjL5Dq/RyTTgz4AgLCg/0L9Zp1QxrEPiqogIlHzMu2AszuYcW6VyNhNUeeM6mXIXvRJx4nKQaK73tefowj0oXLXrDj0aMeIQ7IHowVNOFbWBzeQ64XAQn+eE1Q3ORFUKc8y5WrTmJwWMV1SB72F57HhOgF99DxBeWo0cujVMcE9ZPP0cX7KUSHg+BPuoO3I58OmcxcdThBPG3e18K1+XvZMIe8yz6y6yTvPaluTlXdf8UUSWwXu66HRpi64Z1wQD1MpWFtIlocMwijR6pM5pAwB9ytm7U/VJ19s55GxSAuZG5ZDb87arqgwyOajt1UeQ67xmafbd6dlpQQwOrnbg0qYl7t8fF8dreHekye6g7w4PMGVKpmvfBizv2t1zsj/MpjDfWkK2Gis07DL1lRAPaqlep0CR8VQkD/G9NwyD1yTtdn3rHV9H9xjWicLXstSvrc86ztDPH19N3hFC5b5oJ6SbI= NTSFB9viWn5ym4nNxKvezim/YI9N411yxJboeYcfRqoSaL9MepU+5JzwpyX5eaWG09jeQjBlh41WEmBUqIYUTeLwGzWujZOan8+/YCcfeyAxkFbpHNayLoygw5r7pzOU4JupboPCwYx+xhIvqUroDO67NMG+4Qr1Q09JcrpwR5Jof5neh4561OH8G+hZepMPVfBOSItVfNs1uHm0OYsQdP5qBCIFzsgfESe/JofhoK5PG4sP7Zf8HExov2VngI7IeoS1S6f0BHL41yXJuJoAfBC4cvJsMlBFv/KpujUIhJMQ2CpaB+5kwvIdsAnkbj4dhy8AGPJEZOV3RHJWxuXyZlZMsO3ELhWyQ3GwvRL0P2GMhBA8afGw0MpbfAOuzb7vUEKKyUEbPPECx4jXJ9QztA4jH10PittTO/ibQS0EbLqAGsO48gRygUmAAQElXNKnXP4PazSuQ8bXrRLqhobhotdLTjVA+1ViD3OC1B5jfk2y7n9CcgNJ0ThZYSkDbzpbQI9aLlzdB8CpRrhdNKw1w5D7vQCrsdxwJrDDhnKWbKGqjlggjPdKypCs/k1oFlWZMmSqiu5qYOm81Ufjn3jnxMjKSR3jETJ4JXkve3G8uufcsm40vMCuBKCRMoHAFyPoU66xhKYHnKUBPRRgJ3e9/4gQTPGsUvqeRjDKDeK0rms= ","date":"8 March 2022","externalUrl":null,"permalink":"/posts/custom/encryption/","section":"Posts","summary":"Often times you will want to scramble data before you store it in an LDAP directory or a database. The quickest method is to simply obfuscate the data by replacing each character in the original data with different ones, of course by using a known algorithm. For example, shifting the characters over two places on the ASCII scale before replacement. Once this is done, you would then Base64 encode the data. Not the most secure of methods, but it will work and make the data look like something it is not.\n","title":"Public Key Encryption","type":"posts"},{"content":"","date":"13 March 2021","externalUrl":null,"permalink":"/categories/bespoke/","section":"Categories","summary":"","title":"Bespoke","type":"categories"},{"content":"Belkast Consulting has developed a simple solution that allows one to convert an Microsoft Excel file into an LDIF file. The resulting LDIF file can then be easily imported into any LDAP v3 compliant directory (NetIQ eDirectory, Microsoft Active Directory etc).\nPlease be aware that the code has not been updated in almost a decade so, if you need additional assistance or have a new feature request, please send an email to keith@belkast.com with the subject WEBSITE - EXCEL TO LDIF.\nFeatures # Written in Perl, so 100% cross platform Reads Excel files in the Microsoft Excel 2007 and 2010 file format Ignore list functionality provides a means by which only those columns you are interested in are exported Only those columns that contain data are stored in the output LDIF file A smart date conversion for columns ending with Date A smart substring function can be applied to any column value Works with Microsoft Excel 2007 and 2010 Installation # If a Perl interpreter is not included with your operating system, you must download and install one. Two fine, and reliable, options are Strawberry Perl (for Windows) and ActivePerl.\nOnce Perl is installed, you must download and install the modules listed below. Please install the modules in the order they are listed.\nOLE-Storage_Lite-0.19 Crypt-RC4-2.02 Digest-Perl-MD5-1.8 Spreadsheet-ParseExcel-0.59\nexcel2ldif # This is the actual Perl file which must be executed in order to convert the XLS file into an LDIF file\nignore.txt # This is a comma separated file containing the attribute names which will not be exported to the LDIF file. This file contains only one line.\ntrim.conf # This is a tab delimited file containing the attribute name (first value before the tab) and the substring values (second value after the tab). Before the data value assigned to the attribute is written to the LDIF file, the data value is trimmed according to the substring values. This file contains one attribute per line.\nUsage # To run the conversion process, use perl excel2ldif.pl You will be prompted for the Excel (XLS, XLSX) file to convert:\nThe Excel file should be in a format where: The first row has column headings (this row is used as the attribute names in the exported LDIF file) The first column in each subsequent row should be the full Distinguished Name (DN) of the User Any field which includes a date should end in ‘Date’ (for example: idamEmploymentDate) The date format is dd/mm/yyyy (for example: 11/12/2023) You will be prompted for the ‘ignore’ file: The ignore file is a comma separated list of fields which will be omitted from the exported LDIF You will be prompted for the ‘trim’ file: The trim file is a tab delimited list comprising the attribute name and the substring parameters, with one attribute per line The output file will be the name of the input file with the LDIF extension added. The output file will NOT include those fields which are listed in the ‘ignore’ file. The output file will NOT include those fields where the value is blank in the imported Excel file.\nPlease click here to download the program. The password is ZbqXYQjox4\n","date":"13 March 2021","externalUrl":null,"permalink":"/posts/custom/export-excel-to-ldif/","section":"Posts","summary":"Belkast Consulting has developed a simple solution that allows one to convert an Microsoft Excel file into an LDIF file. The resulting LDIF file can then be easily imported into any LDAP v3 compliant directory (NetIQ eDirectory, Microsoft Active Directory etc).\n","title":"Convert Excel to LDIF","type":"posts"},{"content":"","date":"13 March 2021","externalUrl":null,"permalink":"/tags/perl/","section":"Tag","summary":"","title":"Perl","type":"tags"},{"content":"Some people are blissfully unaware that there is a very powerful command line tool that ships with NetIQ IDM. The command line tool is dxcmd, and it can be used to start and stop Drivers, change Driver startup mode, start Driver Jobs, and start Driver migrations (the purpose of this post).\nECMAScript # The function below creates an XDS document which is used to initiate a migrate using dxcmd.\nfunction createMigrateDocument(paramDestDN, paramClassName, paramSearchAttr, paramSearchAttrValue, paramScope, paramFileLocation) { var now = new Date(); var varFile = new File(paramFileLocation); var out = null; try { out = new BufferedWriter(new FileWriter(varFile)); out.write(\u0026#34;\u0026lt;nds dtdversion=\u0026#34;3.5\u0026#34; ndsversion=\u0026#34;8.x\u0026#34;\u0026gt;\u0026#34;); out.newLine(); out.write(\u0026#34;\u0026lt;source\u0026gt;\u0026#34;); out.newLine(); out.write(\u0026#34;\u0026lt;product version=\u0026#34;3.6.11.5281\u0026#34;\u0026gt;DirXML\u0026lt;/product\u0026gt;\u0026#34;); out.newLine(); out.write(\u0026#34;\u0026lt;contact\u0026gt;Novell, Inc.\u0026lt;/contact\u0026gt;\u0026#34;); out.newLine(); out.write(\u0026#34;\u0026lt;/source\u0026gt;\u0026#34;); out.newLine(); out.write(\u0026#34;\u0026lt;input\u0026gt;\u0026#34;); out.newLine(); out.write(\u0026#34;\u0026lt;query dest-dn=\u0026#34;\u0026#34; + paramDestDN + \u0026#34;\u0026#34; class-name=\u0026#34;\u0026#34; + paramClassName + \u0026#34;\u0026#34; scope=\u0026#34;\u0026#34; + paramScope + \u0026#34;\u0026#34;\u0026gt;\u0026#34;); out.newLine(); out.write(\u0026#34;\u0026lt;search-class class-name=\u0026#34;\u0026#34; + paramClassName + \u0026#34;\u0026#34;/\u0026gt;\u0026#34;); out.newLine(); out.write(\u0026#34;\u0026lt;search-attr attr-name=\u0026#34;\u0026#34; + paramSearchAttr + \u0026#34;\u0026#34;\u0026gt;\u0026#34;); out.newLine(); out.write(\u0026#34;\u0026lt;value\u0026gt;\u0026#34; + paramSearchAttrValue + \u0026#34;\u0026lt;/value\u0026gt;\u0026#34;); out.newLine(); out.write(\u0026#34;\u0026lt;/search-attr\u0026gt;\u0026#34;); out.newLine(); out.write(\u0026#34;\u0026lt;/query\u0026gt;\u0026#34;); out.newLine(); out.write(\u0026#34;\u0026lt;/input\u0026gt;\u0026#34;); out.newLine(); out.write(\u0026#34;\u0026lt;/nds\u0026gt;\u0026#34;); out.newLine(); return varFile.getCanonicalPath(); } catch(ex) { return null; } finally { if(out) { out.close(); return paramFileLocation; } } } } DirXML # The two rules below show how the Connector code calls out to DXCMD. The generated XDS document (created by calling the above script) is passed into the IDM Connector specified in the Local Variable varConnectorDN-Temp.\nThe Policy to which these two Rules belong has the namespace dxcmd defined as shown below.\nxmlns:dxcmd=”http://www.novell.com/nxsl/java/com.novell.nds.dirxml.util.DxCommand” Rule One # \u0026lt;rule\u0026gt; \u0026lt;description\u0026gt;[nrfResource] Set variable for processing a sync\u0026lt;/description\u0026gt; \u0026lt;conditions\u0026gt; \u0026lt;and\u0026gt; \u0026lt;if-class-name op=\u0026#34;equal\u0026#34;\u0026gt;Organizational Unit\u0026lt;/if-class-name\u0026gt; \u0026lt;if-xpath op=\u0026#34;true\u0026#34;\u0026gt;./operation-data/class/@valid-location = \u0026#39;true\u0026#39;\u0026lt;/if-xpath\u0026gt; \u0026lt;if-op-attr name=\u0026#34;$gcvAttribute-TriggerAction$\u0026#34; op=\u0026#34;changing\u0026#34;/\u0026gt; \u0026lt;if-op-attr name=\u0026#34;$gcvAttribute-TriggerAction$\u0026#34; op=\u0026#34;changing-to\u0026#34;\u0026gt;$gcvAttribute-TriggerAction-ValueToSync$\u0026lt;/if-op-attr\u0026gt; \u0026lt;/and\u0026gt; \u0026lt;/conditions\u0026gt; \u0026lt;actions\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varOU-EntitlementDN\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-src-attr name=\u0026#34;idamRFEntitlementDN\u0026#34;/\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varOU-ProcessSync\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-src-attr name=\u0026#34;idamRFAppAutoSync\u0026#34;/\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-trace-message color=\u0026#34;yellow\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-global-variable name=\u0026#34;gcvConnectorName\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt; : \u0026lt;/token-text\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;Processing \u0026#39;\u0026lt;/token-text\u0026gt; \u0026lt;token-src-dn/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;\u0026#39; with entitlement [\u0026lt;/token-text\u0026gt; \u0026lt;token-local-variable name=\u0026#34;varOU-EntitlementDN\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;]\u0026lt;/token-text\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-trace-message\u0026gt; \u0026lt;do-if\u0026gt; \u0026lt;arg-conditions\u0026gt; \u0026lt;and\u0026gt; \u0026lt;if-local-variable mode=\u0026#34;regex\u0026#34; name=\u0026#34;varOU-EntitlementDN\u0026#34; op=\u0026#34;equal\u0026#34;\u0026gt;^.+$\u0026lt;/if-local-variable\u0026gt; \u0026lt;if-local-variable name=\u0026#34;varOU-ProcessSync\u0026#34; op=\u0026#34;equal\u0026#34;\u0026gt;true\u0026lt;/if-local-variable\u0026gt; \u0026lt;/and\u0026gt; \u0026lt;/arg-conditions\u0026gt; \u0026lt;arg-actions\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varSource-XMLData-FinalLocation\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-src-dn/\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varProcessSync\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;true\u0026lt;/token-text\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;/arg-actions\u0026gt; \u0026lt;/do-if\u0026gt; \u0026lt;/actions\u0026gt; \u0026lt;/rule\u0026gt; Rule Two # \u0026lt;rule\u0026gt; \u0026lt;description\u0026gt;[nrfResource, Organizational Unit] - Valid change was detected, process \u0026#39;sync\u0026#39;\u0026lt;/description\u0026gt; \u0026lt;conditions\u0026gt; \u0026lt;and\u0026gt; \u0026lt;if-local-variable mode=\u0026#34;regex\u0026#34; name=\u0026#34;varProcessSync\u0026#34; op=\u0026#34;equal\u0026#34;\u0026gt;true\u0026lt;/if-local-variable\u0026gt; \u0026lt;/and\u0026gt; \u0026lt;/conditions\u0026gt; \u0026lt;actions\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varConnectorDN-Temp\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-src-attr name=\u0026#34;idamRFConnectorDN\u0026#34;\u0026gt; \u0026lt;arg-dn\u0026gt; \u0026lt;token-local-variable name=\u0026#34;varSource-XMLData-FinalLocation\u0026#34;/\u0026gt; \u0026lt;/arg-dn\u0026gt; \u0026lt;/token-src-attr\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varConnectorLocation\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-src-attr name=\u0026#34;L\u0026#34;\u0026gt; \u0026lt;arg-dn\u0026gt; \u0026lt;token-xpath expression=\u0026#34;./operation-data/source-parent-dn/text()\u0026#34;/\u0026gt; \u0026lt;/arg-dn\u0026gt; \u0026lt;/token-src-attr\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varConnectorDN\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-parse-dn dest-dn-format=\u0026#34;dot\u0026#34; start=\u0026#34;1\u0026#34;\u0026gt; \u0026lt;token-local-variable name=\u0026#34;varConnectorDN-Temp\u0026#34;/\u0026gt; \u0026lt;/token-parse-dn\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varUsername\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-parse-dn dest-dn-format=\u0026#34;dot\u0026#34; src-dn-format=\u0026#34;ldap\u0026#34;\u0026gt; \u0026lt;token-global-variable name=\u0026#34;gcvUser-UserApplicationAdmin\u0026#34;/\u0026gt; \u0026lt;/token-parse-dn\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varQuery-SearchBase\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-src-attr name=\u0026#34;idamRFAppSourceBaseLocation\u0026#34;\u0026gt; \u0026lt;arg-dn\u0026gt; \u0026lt;token-local-variable name=\u0026#34;varSource-XMLData-FinalLocation\u0026#34;/\u0026gt; \u0026lt;/arg-dn\u0026gt; \u0026lt;/token-src-attr\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-if\u0026gt; \u0026lt;arg-conditions\u0026gt; \u0026lt;or\u0026gt; \u0026lt;if-local-variable mode=\u0026#34;regex\u0026#34; name=\u0026#34;varConnectorDN\u0026#34; op=\u0026#34;not-equal\u0026#34;\u0026gt;^.+$\u0026lt;/if-local-variable\u0026gt; \u0026lt;if-local-variable mode=\u0026#34;regex\u0026#34; name=\u0026#34;varQuery-SearchBase\u0026#34; op=\u0026#34;not-equal\u0026#34;\u0026gt;^.+$\u0026lt;/if-local-variable\u0026gt; \u0026lt;/or\u0026gt; \u0026lt;/arg-conditions\u0026gt; \u0026lt;arg-actions\u0026gt; \u0026lt;do-trace-message color=\u0026#34;yellow\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-global-variable name=\u0026#34;gcvConnectorName\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;: \u0026lt;/token-text\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;EMPTY SEARCH BASE - BREAKING!\u0026lt;/token-text\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-trace-message\u0026gt; \u0026lt;do-break/\u0026gt; \u0026lt;/arg-actions\u0026gt; \u0026lt;/do-if\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varCurrentJavaDate\u0026#34;\u0026gt; \u0026lt;arg-object\u0026gt; \u0026lt;token-xpath expression=\u0026#34;jdate:new()\u0026#34;/\u0026gt; \u0026lt;/arg-object\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varNDSDate\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-xpath expression=\u0026#34;jdate:getTime($varCurrentJavaDate)\u0026#34;/\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varQueryFileName\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-global-variable name=\u0026#34;gcvQueryFile-BaseLocation\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;IDAM_Migrate_\u0026lt;/token-text\u0026gt; \u0026lt;token-local-variable name=\u0026#34;varNDSDate\u0026#34;/\u0026gt; \u0026lt;!-- \u0026lt;token-parse-dn src-dn-format=\u0026#34;slash\u0026#34; start=\u0026#34;-1\u0026#34;\u0026gt;--\u0026gt; \u0026lt;!-- \u0026lt;token-global-variable name=\u0026#34;dirxml.auto.driverdn\u0026#34;/\u0026gt;--\u0026gt; \u0026lt;!-- \u0026lt;/token-parse-dn\u0026gt;--\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varQueryDocFile\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-xpath expression=\u0026#34;es:createMigrateDocument($varQuery-SearchBase, \u0026#39;Group\u0026#39;, \u0026#39;CN\u0026#39;, \u0026#39;*\u0026#39;, \u0026#39;subtree\u0026#39;, $varQueryFileName)\u0026#34;/\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varCommand\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;-host \u0026lt;/token-text\u0026gt; \u0026lt;token-local-variable name=\u0026#34;varConnectorLocation\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;-user \u0026lt;/token-text\u0026gt; \u0026lt;token-local-variable name=\u0026#34;varUsername\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;-password \u0026lt;/token-text\u0026gt; \u0026lt;token-named-password name=\u0026#34;npUserApplication-ProvisioningAdmin-Password\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;-migrateapp \u0026lt;/token-text\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;\u0026#39;\u0026lt;/token-text\u0026gt; \u0026lt;token-local-variable name=\u0026#34;varConnectorDN\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;\u0026#39;\u0026lt;/token-text\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;/\u0026gt; \u0026lt;token-local-variable name=\u0026#34;varQueryDocFile\u0026#34;/\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-trace-message color=\u0026#34;yellow\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-global-variable name=\u0026#34;gcvConnectorName\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;: \u0026lt;/token-text\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;Calling dxcmd for Connector \u0026#39;\u0026lt;/token-text\u0026gt; \u0026lt;token-local-variable name=\u0026#34;varConnectorDN\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;\u0026#39; with command line [\u0026lt;/token-text\u0026gt; \u0026lt;token-local-variable name=\u0026#34;varCommand\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;]\u0026lt;/token-text\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-trace-message\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varResult\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-xpath expression=\u0026#34;dxcmd:commandLine(string($varCommand))\u0026#34;/\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-if\u0026gt; \u0026lt;arg-conditions\u0026gt; \u0026lt;and\u0026gt; \u0026lt;if-xpath op=\u0026#34;true\u0026#34;\u0026gt;$varResult = -1\u0026lt;/if-xpath\u0026gt; \u0026lt;/and\u0026gt; \u0026lt;/arg-conditions\u0026gt; \u0026lt;arg-actions\u0026gt; \u0026lt;do-trace-message\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;The DXCMD command completed with an ERROR.\u0026lt;/token-text\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-trace-message\u0026gt; \u0026lt;/arg-actions\u0026gt; \u0026lt;arg-actions\u0026gt; \u0026lt;do-trace-message\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;The DXCMD command completed SUCCESSFULLY.\u0026lt;/token-text\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-trace-message\u0026gt; \u0026lt;/arg-actions\u0026gt; \u0026lt;/do-if\u0026gt; \u0026lt;/actions\u0026gt; \u0026lt;/rule\u0026gt; ","date":"12 January 2021","externalUrl":null,"permalink":"/posts/code/idm/idm-migrate-with-dxcmd/","section":"Posts","summary":"Some people are blissfully unaware that there is a very powerful command line tool that ships with NetIQ IDM. The command line tool is dxcmd, and it can be used to start and stop Drivers, change Driver startup mode, start Driver Jobs, and start Driver migrations (the purpose of this post).\n","title":"Migrate using DXCMD","type":"posts"},{"content":"We were recently asked to provide code which satisfied the following specification:\nIn flowdata for a PRD I am trying to do the following in a mapping activity. I have a string, shown below 2012-06-20T12:26:32.000+02:002012-07-17T12:26:32.000+02:002422224300cn=ADMKEVRAS,ou=sa,o=dataTest 11[DN on a group] This string is just a stringified XML stored in a Case Ignore String attribute in edirectory. In clean XML it looks like this: \u0026lt;data\u0026gt; \u0026lt;startdate\u0026gt;2012-06-20T12:26:32.000+02:00\u0026lt;/startdate\u0026gt; \u0026lt;enddate\u0026gt;2012-07-17T12:26:32.000+02:00\u0026lt;/enddate\u0026gt; \u0026lt;udlaaner\u0026gt;24222\u0026lt;/udlaaner\u0026gt; \u0026lt;modtager\u0026gt;24300\u0026lt;/modtager\u0026gt; \u0026lt;initiator\u0026gt;cn=ADMKEVRAS,ou=sa,o=data\u0026lt;/initiator\u0026gt; \u0026lt;desc\u0026gt; \u0026lt;value\u0026gt;Test 11\u0026lt;/value\u0026gt; \u0026lt;/desc\u0026gt; \u0026lt;A1\u0026gt;[DN on a group]\u0026lt;/A1\u0026gt; \u0026lt;/data\u0026gt; I need to convert this string to an XML document in order to add one more child node to the node and also to change the value of the node. After this I need to stringify the XML document again in order to save the updated information to the String attribute in edirectory. The Solution In order to do what the request asks, the following steps are required:\nIf using a version of Novell IDM prior to 4, you’ll need to add this ECMAScript as a function to whichever mapping activity requires it. If using Novell IDM version 4 and above, you can add this script as a global script and attach it to the Workflow directly; the code can then be called from all mapping activities within that Workflow Read the string out of the directory Ensure the string can be converted to an XML document (replace the \u003c and \u003e tags) Convert the string into an XML document Add the new node and replace the text in the ... node Convert the XML document back into a String, and store it in the directory The JavaScript / ECMAScript code for the solution is shown below function String2XML(varString) { // Let\u0026#39;s set a few variables so we can test the code a bit better var varNameOfNewDescNode = \u0026#39;new-desc-node\u0026#39;; var varValueOfNewDescNode = \u0026#39;This is a value\u0026#39;; var varNewValueForStartDate = \u0026#39;BELKAST CONSULTING\u0026#39; var varNewValueforEndDate = \u0026#39;Keith Armstrong\u0026#39; // #################### // Let\u0026#39;s just be sure, and get values of \u0026#39;\u0026lt;\u0026#39; and \u0026#39;\u0026gt;\u0026#39; in the replacement value var varRegexGT = new RegExp(\u0026#34;\u0026gt;\u0026#34;, \u0026#34;g\u0026#34;); var varRegexLT = new RegExp(\u0026#34;\u0026lt;\u0026#34;, \u0026#34;g\u0026#34;); varString = varString.replace(varRegexGT, \u0026#34;\u0026gt;\u0026#34;); varString = varString.replace(varRegexLT, \u0026#34;\u0026amp;lt;\u0026#34;); var dbf = Packages.javax.xml.parsers.DocumentBuilderFactory.newInstance(); var builder = dbf.newDocumentBuilder(); var stringreader = Packages.java.io.StringReader; var strinput = new stringreader(varString); var inputsource = new Packages.org.xml.sax.InputSource(strinput); var doc = builder.parse(inputsource); // Now let\u0026#39;s read the desc element...we need to add something on there var nodeList = doc.getElementsByTagName(\u0026#39;desc\u0026#39;); var node = nodeList.item(0); var newDescNode = doc.createElement(varNameOfNewDescNode); newDescNode.appendChild(doc.createTextNode(varValueOfNewDescNode)); node.appendChild(newDescNode); // Now let\u0026#39;s get the start date element, and put our own value in there var nodeList = doc.getElementsByTagName(\u0026#39;startdate\u0026#39;); var node = nodeList.item(0); node.textContent = varNewValueForStartDate; // Now let\u0026#39;s get the end date element, and put our own value in there var nodeList = doc.getElementsByTagName(\u0026#39;enddate\u0026#39;); var node = nodeList.item(0); node.textContent = varNewValueforEndDate; // Now we need to convert the XML back to a string to store in the attribute domSource = new Packages.javax.xml.transform.dom.DOMSource(doc); writer = new Packages.java.io.StringWriter(); result = new Packages.javax.xml.transform.stream.StreamResult(writer); tf = Packages.javax.xml.transform.TransformerFactory.newInstance(); transformer = tf.newTransformer(); transformer.transform(domSource, result); writer.flush(); return writer.toString(); } The string started off looking like this: 2012-06-20T12:26:32.000+02:002012-07-17T12:26:32.000+02:002422224300cn=ADMKEVRAS,ou=sa,o=dataTest 11[DN on a group] The string, once manipulated, looked like this: \u003c?xml version=”1.0″ encoding=”UTF-8″?\u003eBELKAST CONSULTINGKeith Armstrong2422224300cn=ADMKEVRAS,ou=sa,o=dataTest 11This is a value[DN on a group] ","date":"12 January 2021","externalUrl":null,"permalink":"/posts/code/idm/add-nodes-to-xml-doc/","section":"Posts","summary":"We were recently asked to provide code which satisfied the following specification:\nIn flowdata for a PRD I am trying to do the following in a mapping activity. I have a string, shown below 2012-06-20T12:26:32.000+02:002012-07-17T12:26:32.000+02:002422224300cn=ADMKEVRAS,ou=sa,o=dataTest 11[DN on a group] This string is just a stringified XML stored in a Case Ignore String attribute in edirectory. In clean XML it looks like this: \u003cdata\u003e \u003cstartdate\u003e2012-06-20T12:26:32.000+02:00\u003c/startdate\u003e \u003cenddate\u003e2012-07-17T12:26:32.000+02:00\u003c/enddate\u003e \u003cudlaaner\u003e24222\u003c/udlaaner\u003e \u003cmodtager\u003e24300\u003c/modtager\u003e \u003cinitiator\u003ecn=ADMKEVRAS,ou=sa,o=data\u003c/initiator\u003e \u003cdesc\u003e \u003cvalue\u003eTest 11\u003c/value\u003e \u003c/desc\u003e \u003cA1\u003e[DN on a group]\u003c/A1\u003e \u003c/data\u003e I need to convert this string to an XML document in order to add one more child node to the node and also to change the value of the node. After this I need to stringify the XML document again in order to save the updated information to the String attribute in edirectory. The Solution In order to do what the request asks, the following steps are required:\n","title":"Add nodes to XML document","type":"posts"},{"content":"If you have your NetIQ IDM Engine on eDirectory which is running on a Windows Server, then you might just want to call PowerShell directly from the Engine; at least you have the capability of doing so.\nThe following code snippets (both DirXML Script and ECMAScript) will allow you to do just that. Of course, this only works on Windows, but it’s a good alternative if you would like any NetIQ IDM Driver to be able to execute PowerShell scripts.\nDirXML Script The following code will create a Home Folder when a User is created in Active Directory. \u0026lt;rule\u0026gt; \u0026lt;description\u0026gt;[Windows] Create Home Folder Using Remote Powershell\u0026lt;/description\u0026gt; \u0026lt;conditions\u0026gt; \u0026lt;and\u0026gt; \u0026lt;if-global-variable mode=\u0026#34;nocase\u0026#34; name=\u0026#34;gcvHomeFolder-Script-OperatingSystem\u0026#34; op=\u0026#34;equal\u0026#34;\u0026gt;winblows\u0026lt;/if-global-variable\u0026gt; \u0026lt;if-operation op=\u0026#34;equal\u0026#34;\u0026gt;status\u0026lt;/if-operation\u0026gt; \u0026lt;if-xpath op=\u0026#34;true\u0026#34;\u0026gt;self::status[@level=\u0026#39;success\u0026#39;]\u0026lt;/if-xpath\u0026gt; \u0026lt;if-xpath op=\u0026#34;true\u0026#34;\u0026gt;./operation-data/class/text() = \u0026#39;User\u0026#39;\u0026lt;/if-xpath\u0026gt; \u0026lt;if-op-property mode=\u0026#34;nocase\u0026#34; name=\u0026#34;final-operation\u0026#34; op=\u0026#34;equal\u0026#34;\u0026gt;add\u0026lt;/if-op-property\u0026gt; \u0026lt;if-xpath op=\u0026#34;true\u0026#34;\u0026gt;string-length(./operation-data/aib-info/home-folder/text()) \u0026gt; 0\u0026lt;/if-xpath\u0026gt; \u0026lt;/and\u0026gt; \u0026lt;/conditions\u0026gt; \u0026lt;actions\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varUserName\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-xpath expression=\u0026#34;./operation-data/user-info/CN/text()\u0026#34;/\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varHomeFolder\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-xpath expression=\u0026#34;./operation-data/aib-info/home-folder/text()\u0026#34;/\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varRunAs_UN\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-named-password name=\u0026#34;np-HomeFolder-Username\u0026#34;/\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varRunAs_PW\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-named-password name=\u0026#34;np-HomeFolder-Password\u0026#34;/\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varCallType\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-global-variable name=\u0026#34;gcvHomeFolder-Script-PowershellActive\u0026#34;/\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-trace-message color=\u0026#34;brgreen\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-global-variable name=\u0026#34;ConnectedSystemName\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt; : \u0026lt;/token-text\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;About to create a Home Folder for User [\u0026lt;/token-text\u0026gt; \u0026lt;token-xpath expression=\u0026#34;./operation-data/user-info/CN/text()\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;] on [\u0026lt;/token-text\u0026gt; \u0026lt;token-xpath expression=\u0026#34;./operation-data/aib-info/home-folder/text()\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;] [\u0026lt;/token-text\u0026gt; \u0026lt;token-global-variable name=\u0026#34;gcvHomeFolder-Script-PowershellExe\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;] [\u0026lt;/token-text\u0026gt; \u0026lt;token-global-variable name=\u0026#34;gcvHomeFolder-Script-PowershellStub\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;] [\u0026lt;/token-text\u0026gt; \u0026lt;token-global-variable name=\u0026#34;gcvHomeFolder-Script-PowershellActive\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;] {\u0026lt;/token-text\u0026gt; \u0026lt;token-local-variable name=\u0026#34;varRunAs_UN\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt; \u0026lt;/token-text\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;}\u0026lt;/token-text\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-trace-message\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varPS_Result\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-xpath expression=\u0026#34;es:run_ps_scripts(\u0026#39;~gcvHomeFolder-Script-PowershellExe~\u0026#39;,\u0026#39;~gcvHomeFolder-Script-PowershellStub~\u0026#39;,\u0026#39;~gcvHomeFolder-Script-PowershellActive~\u0026#39;,$varUserName,$varHomeFolder,$varRunAs_UN,$varRunAs_PW)\u0026#34;/\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;/actions\u0026gt; \u0026lt;/rule\u0026gt; ECMAScript Function importClass(java.lang.ProcessBuilder); importClass(java.io.InputStreamReader); importClass(java.io.BufferedReader); importClass(java.lang.System); importClass(java.util.ArrayList); importClass(java.util.List); function run_ps_scripts (power_shell, first_script, second_script, username, location, ra_un, ra_pw) { var varList = java.util.ArrayList(); varList.add(power_shell); varList.add(\u0026#34;-noprofile\u0026#34;); varList.add(\u0026#34;-nologo\u0026#34;); varList.add(\u0026#34;-noninteractive\u0026#34;); varList.add(\u0026#34;-F\u0026#34;); varList.add(first_script); varList.add(second_script); varList.add(username); varList.add(location); varList.add(ra_un); varList.add(ra_pw); var varProBuilder = java.lang.ProcessBuilder (varList); java.lang.System.out.println(varProBuilder.command()); var process = varProBuilder.start(); is = process.getInputStream(); isr = java.io.InputStreamReader(is); br = java.io.BufferedReader(isr); var line; try { exitValue = process.waitFor(); java.lang.System.out.println(\u0026#34;\\nProgram exit was [\u0026#34; + exitValue + \u0026#34;]\u0026#34;); return exitValue; } catch (e) { e.printStackTrace(); return -1; } } ","date":"12 January 2021","externalUrl":null,"permalink":"/posts/code/idm/idm-call-powershell/","section":"Posts","summary":"If you have your NetIQ IDM Engine on eDirectory which is running on a Windows Server, then you might just want to call PowerShell directly from the Engine; at least you have the capability of doing so.\n","title":"Calling PowerShell","type":"posts"},{"content":"It is best practice to implement most of the lifecycle logic in a Service Driver when receiving data from an authoritative source. Some examples are shown below:\nInitial assignment of a password Termination and rehire of an employee Transfer of an employee Assignment of Group(s) on Creation Email notification and auditing on Creation Assignment and re-assignment of Manager ","date":"12 January 2021","externalUrl":null,"permalink":"/posts/code/idm/idm-default-values/","section":"Posts","summary":"It is best practice to implement most of the lifecycle logic in a Service Driver when receiving data from an authoritative source. Some examples are shown below:\nInitial assignment of a password Termination and rehire of an employee Transfer of an employee Assignment of Group(s) on Creation Email notification and auditing on Creation Assignment and re-assignment of Manager ","title":"Implementing a Service Driver","type":"posts"},{"content":"The mapping-table functionality in Micro Focus IDM is a great feature to store ‘IF A THEN RETURN B’ results. Unfortunately, this is as far as the logic can be stretched. Basically, the mapping-table allows data to be stored and retrieved based on only one condition. Therefore, it is not possible to say ‘IF A AND B THEN RETURN C’. There is, however, a solution to this problem: store the mapping-table in operation-data and use XPATH to retrieve the data you are looking for.\nBy using XPATH, you can retrieve data using the code shown below.\n\u0026lt;do-set-local-variable name=\u0026#34;varMappingTableData\u0026#34;\u0026gt; \u0026lt;arg-node-set\u0026gt; \u0026lt;token-xpath expression=\u0026#34;./operation-data/mapping-table/row/@columnC[contains(../@columnA,\u0026#39;value1\u0026#39;) and contains(../@columnB,\u0026#39;value2\u0026#39;)]\u0026#34;/\u0026gt; \u0026lt;/arg-node-set\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; This code will return, in a node-set, those values contained in column C where column A and column B resolve to ‘true’. The code to store a mapping-table in operation-data is shown below, along with a sample mapping-table. Once the data is stored, it is up to you to determine how best to use XPATH to get at the data you want.\nThe Mapping Table # \u0026lt;mapping-table\u0026gt; \u0026lt;col-def name=\u0026#34;attribute-name\u0026#34;/\u0026gt; \u0026lt;col-def name=\u0026#34;attribute-type\u0026#34;/\u0026gt; \u0026lt;col-def name=\u0026#34;required\u0026#34;/\u0026gt; \u0026lt;col-def name=\u0026#34;default-value\u0026#34;/\u0026gt; \u0026lt;col-def name=\u0026#34;mapping\u0026#34;/\u0026gt; \u0026lt;row\u0026gt; \u0026lt;col\u0026gt;nspmDistributionPassword\u0026lt;/col\u0026gt; \u0026lt;col\u0026gt;source\u0026lt;/col\u0026gt; \u0026lt;col\u0026gt;true\u0026lt;/col\u0026gt; \u0026lt;col/\u0026gt; \u0026lt;col\u0026gt;@@password@@\u0026lt;/col\u0026gt; \u0026lt;/row\u0026gt; \u0026lt;row\u0026gt; \u0026lt;col\u0026gt;Password Expiration Time\u0026lt;/col\u0026gt; \u0026lt;col\u0026gt;source\u0026lt;/col\u0026gt; \u0026lt;col\u0026gt;true\u0026lt;/col\u0026gt; \u0026lt;col/\u0026gt; \u0026lt;col\u0026gt;@@expirationTime@@\u0026lt;/col\u0026gt; \u0026lt;/row\u0026gt; \u0026lt;/mapping-table\u0026gt; The Code # \u0026lt;rule\u0026gt; \u0026lt;description\u0026gt;[Mapping-table] Add in operation-data for attributes\u0026lt;/description\u0026gt; \u0026lt;conditions\u0026gt; \u0026lt;and\u0026gt; \u0026lt;if-xpath op=\u0026#34;true\u0026#34;\u0026gt;string-length(./@webservice-mapping) \u0026gt; 0\u0026lt;/if-xpath\u0026gt; \u0026lt;/and\u0026gt; \u0026lt;/conditions\u0026gt; \u0026lt;actions\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varMappingTableLocation\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-xpath expression=\u0026#34;./@webservice-mapping\u0026#34;/\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varMappingTable\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-node-set\u0026gt; \u0026lt;token-xml-parse\u0026gt; \u0026lt;token-base64-decode\u0026gt; \u0026lt;token-src-attr name=\u0026#34;DirXML-Data\u0026#34;\u0026gt; \u0026lt;arg-dn\u0026gt; \u0026lt;token-xpath expression=\u0026#34;$varMappingTableLocation\u0026#34;/\u0026gt; \u0026lt;/arg-dn\u0026gt; \u0026lt;/token-src-attr\u0026gt; \u0026lt;/token-base64-decode\u0026gt; \u0026lt;/token-xml-parse\u0026gt; \u0026lt;/arg-node-set\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varColumnNumber\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-xpath expression=\u0026#34;count($varMappingTable/mapping-table/col-def)\u0026#34;/\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-append-xml-element expression=\u0026#34;./operation-data\u0026#34; name=\u0026#34;webservice-data\u0026#34;/\u0026gt; \u0026lt;do-for-each\u0026gt; \u0026lt;arg-node-set\u0026gt; \u0026lt;token-xpath expression=\u0026#34;$varMappingTable/mapping-table/row\u0026#34;/\u0026gt; \u0026lt;/arg-node-set\u0026gt; \u0026lt;arg-actions\u0026gt; \u0026lt;do-append-xml-element expression=\u0026#34;./operation-data/webservice-data\u0026#34; name=\u0026#34;attribute\u0026#34;/\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varCounter\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-text\u0026gt;0\u0026lt;/token-text\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-while\u0026gt; \u0026lt;arg-conditions\u0026gt; \u0026lt;and\u0026gt; \u0026lt;if-local-variable mode=\u0026#34;numeric\u0026#34; name=\u0026#34;varCounter\u0026#34; op=\u0026#34;lt\u0026#34;\u0026gt;$varColumnNumber$\u0026lt;/if-local-variable\u0026gt; \u0026lt;/and\u0026gt; \u0026lt;/arg-conditions\u0026gt; \u0026lt;arg-actions\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varHeader\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-xpath expression=\u0026#34;$varMappingTable//col-def[$varCounter + 1]/@name\u0026#34;/\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varValue\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-xpath expression=\u0026#34;$current-node//col[$varCounter + 1]/text()\u0026#34;/\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varCounter\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-xpath expression=\u0026#34;$varCounter + 1\u0026#34;/\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-trace-message color=\u0026#34;brblue\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;Parsing column [\u0026lt;/token-text\u0026gt; \u0026lt;token-local-variable name=\u0026#34;varCounter\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;] of mapping-table [\u0026lt;/token-text\u0026gt; \u0026lt;token-local-variable name=\u0026#34;varMappingTableLocation\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;] - adding header \u0026#39;\u0026lt;/token-text\u0026gt; \u0026lt;token-local-variable name=\u0026#34;varHeader\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;\u0026#39; with value \u0026#39;\u0026lt;/token-text\u0026gt; \u0026lt;token-local-variable name=\u0026#34;varValue\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;\u0026#39; to webservice-data\u0026lt;/token-text\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-trace-message\u0026gt; \u0026lt;do-set-xml-attr expression=\u0026#34;./operation-data/webservice-data/attribute[last()]\u0026#34; name=\u0026#34;$varHeader$\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-xpath expression=\u0026#34;$varValue\u0026#34;/\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-xml-attr\u0026gt; \u0026lt;/arg-actions\u0026gt; \u0026lt;/do-while\u0026gt; \u0026lt;/arg-actions\u0026gt; \u0026lt;/do-for-each\u0026gt; \u0026lt;/actions\u0026gt; \u0026lt;/rule\u0026gt; ","date":"12 January 2021","externalUrl":null,"permalink":"/posts/code/idm/idm-mapping-table-query/","section":"Posts","summary":"The mapping-table functionality in Micro Focus IDM is a great feature to store ‘IF A THEN RETURN B’ results. Unfortunately, this is as far as the logic can be stretched. Basically, the mapping-table allows data to be stored and retrieved based on only one condition. Therefore, it is not possible to say ‘IF A AND B THEN RETURN C’. There is, however, a solution to this problem: store the mapping-table in operation-data and use XPATH to retrieve the data you are looking for.\n","title":"Query Mapping Table","type":"posts"},{"content":"Sometimes there is a requirement to read the IDM Connector filter and process the data.\nSet a node-set local variable for the Driver DN (not shown) Query the Driver, and read three attributes. One of the attributes read is the DirXML-DriverFilter attribute Set a node-set local variable for the Filter. The DirXML-DriverFilter attribute is actually Base64 encoded, so this needs to be decoded Use a simple XPath function to return the value of @subscriber for the User Class The code below shows how to accomplish this.\n\u0026lt;do-set-local-variable name=\u0026#34;varDriverQuery\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-node-set\u0026gt; \u0026lt;token-query scope=\u0026#34;entry\u0026#34;\u0026gt; \u0026lt;arg-dn\u0026gt; \u0026lt;token-local-variable name=\u0026#34;varDriverDN\u0026#34;/\u0026gt; \u0026lt;/arg-dn\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;GUID\u0026lt;/token-text\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;DirXML-DriverFilter\u0026lt;/token-text\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;DirXML-Policies\u0026lt;/token-text\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/token-query\u0026gt; \u0026lt;/arg-node-set\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varDriverFilter\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-node-set\u0026gt; \u0026lt;token-xml-parse\u0026gt; \u0026lt;token-base64-decode\u0026gt; \u0026lt;token-xpath expression=\u0026#34;$varDriverQuery/attr[@attr-name=\u0026#39;DirXML-DriverFilter\u0026#39;]/value/text()\u0026#34;/\u0026gt; \u0026lt;/token-base64-decode\u0026gt; \u0026lt;/token-xml-parse\u0026gt; \u0026lt;/arg-node-set\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varSubscriberOption\u0026#34; scope=\u0026#34;policy\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-xpath expression=\u0026#34;$varDriverFilter/filter/filter-class[@class-name=\u0026#39;User\u0026#39;]/@subscriber\u0026#34;/\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; ","date":"12 January 2021","externalUrl":null,"permalink":"/posts/code/idm/idm-read-filter/","section":"Posts","summary":"Sometimes there is a requirement to read the IDM Connector filter and process the data.\nSet a node-set local variable for the Driver DN (not shown) Query the Driver, and read three attributes. One of the attributes read is the DirXML-DriverFilter attribute Set a node-set local variable for the Filter. The DirXML-DriverFilter attribute is actually Base64 encoded, so this needs to be decoded Use a simple XPath function to return the value of @subscriber for the User Class The code below shows how to accomplish this.\n","title":"Read the IDM filter as XML","type":"posts"},{"content":"The code listed below provides a better way to determine when an Object should be created in a NetIQ IDM Connector.\nUsually it is very common to use the verb \u0026lt;veto-if-attribute-not-available\u0026gt;. This is neither advisable nor efficient. When using this method, there is no information given regarding the reason for the veto, and there is no way to handle multiple missing required attributes; the veto will happen when the first missing attribute is encountered.\nGlobal Configuration Variable # \u0026lt;definition display-name=\u0026#34;Required attributes for User Object Create [Employee]\u0026#34; item-separator=\u0026#34;|\u0026#34; name=\u0026#34;gcvEmployeeRequiredAttributes\u0026#34; type=\u0026#34;list\u0026#34;\u0026gt; \u0026lt;description/\u0026gt; \u0026lt;value\u0026gt; \u0026lt;item\u0026gt;CN\u0026lt;/item\u0026gt; \u0026lt;item\u0026gt;Surname\u0026lt;/item\u0026gt; \u0026lt;item\u0026gt;Given Name\u0026lt;/item\u0026gt; \u0026lt;item\u0026gt;workforceID\u0026lt;/item\u0026gt; \u0026lt;item\u0026gt;upPersonDepartmentCode\u0026lt;/item\u0026gt; \u0026lt;item\u0026gt;upPersonSubDepartmentCode\u0026lt;/item\u0026gt; \u0026lt;item\u0026gt;upPersonSocialSecurityNumber\u0026lt;/item\u0026gt; \u0026lt;item\u0026gt;upPersonStatusCode\u0026lt;/item\u0026gt; \u0026lt;item\u0026gt;upPersonUserType\u0026lt;/item\u0026gt; \u0026lt;/value\u0026gt; \u0026lt;/definition\u0026gt; DirXML Policy # \u0026lt;rule\u0026gt; \u0026lt;description\u0026gt;[User] Check required attributes for Create [Employee]\u0026lt;/description\u0026gt; \u0026lt;conditions\u0026gt; \u0026lt;and\u0026gt; \u0026lt;if-class-name op=\u0026#34;equal\u0026#34;\u0026gt;User\u0026lt;/if-class-name\u0026gt; \u0026lt;if-op-attr name=\u0026#34;upPersonUserType\u0026#34; op=\u0026#34;equal\u0026#34;\u0026gt;~gcvEmployeeUserType~\u0026lt;/if-op-attr\u0026gt; \u0026lt;/and\u0026gt; \u0026lt;/conditions\u0026gt; \u0026lt;actions\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varXDSDocumentNodes\u0026#34;\u0026gt; \u0026lt;arg-node-set\u0026gt; \u0026lt;token-xpath expression=\u0026#34;add-attr/@attr-name\u0026#34;/\u0026gt; \u0026lt;/arg-node-set\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-for-each\u0026gt; \u0026lt;arg-node-set\u0026gt; \u0026lt;token-global-variable name=\u0026#34;gcvEmployeeRequiredAttributes\u0026#34;/\u0026gt; \u0026lt;/arg-node-set\u0026gt; \u0026lt;arg-actions\u0026gt; \u0026lt;do-trace-message color=\u0026#34;brblue\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-global-variable name=\u0026#34;gcvConnectorName\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;:\u0026lt;/token-text\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;Checking existence of required attribute for Employee: \u0026lt;/token-text\u0026gt; \u0026lt;token-local-variable name=\u0026#34;current-node\u0026#34;/\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-trace-message\u0026gt; \u0026lt;do-if\u0026gt; \u0026lt;arg-conditions\u0026gt; \u0026lt;and\u0026gt; \u0026lt;if-xpath op=\u0026#34;not-true\u0026#34;\u0026gt;$current-node = $varXDSDocumentNodes\u0026lt;/if-xpath\u0026gt; \u0026lt;/and\u0026gt; \u0026lt;/arg-conditions\u0026gt; \u0026lt;arg-actions\u0026gt; \u0026lt;do-trace-message color=\u0026#34;brblue\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-global-variable name=\u0026#34;gcvConnectorName\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;:\u0026lt;/token-text\u0026gt; \u0026lt;token-local-variable name=\u0026#34;current-node\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;is missing!!\u0026lt;/token-text\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-trace-message\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varUserMissingAttributes\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-local-variable name=\u0026#34;varUserMissingAttributes\u0026#34;/\u0026gt; \u0026lt;token-local-variable name=\u0026#34;current-node\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;;\u0026lt;/token-text\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;do-set-local-variable name=\u0026#34;varUserCreateError\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;true\u0026lt;/token-text\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-set-local-variable\u0026gt; \u0026lt;/arg-actions\u0026gt; \u0026lt;arg-actions\u0026gt; \u0026lt;do-trace-message color=\u0026#34;brblue\u0026#34;\u0026gt; \u0026lt;arg-string\u0026gt; \u0026lt;token-global-variable name=\u0026#34;gcvConnectorName\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;:\u0026lt;/token-text\u0026gt; \u0026lt;token-local-variable name=\u0026#34;current-node\u0026#34;/\u0026gt; \u0026lt;token-text xml:space=\u0026#34;preserve\u0026#34;\u0026gt;checked out OK!!!\u0026lt;/token-text\u0026gt; \u0026lt;/arg-string\u0026gt; \u0026lt;/do-trace-message\u0026gt; \u0026lt;/arg-actions\u0026gt; \u0026lt;/do-if\u0026gt; \u0026lt;/arg-actions\u0026gt; \u0026lt;/do-for-each\u0026gt; \u0026lt;/actions\u0026gt; \u0026lt;/rule\u0026gt; ","date":"12 January 2021","externalUrl":null,"permalink":"/posts/code/idm/idm-rewrite-veto/","section":"Posts","summary":"The code listed below provides a better way to determine when an Object should be created in a NetIQ IDM Connector.\nUsually it is very common to use the verb \u003cveto-if-attribute-not-available\u003e. This is neither advisable nor efficient. When using this method, there is no information given regarding the reason for the veto, and there is no way to handle multiple missing required attributes; the veto will happen when the first missing attribute is encountered.\n","title":"Rewrite Veto","type":"posts"},{"content":"Our primary focus lies in the development of NetIQ Identity Manager drivers, an area in which we have cultivated extensive expertise over the years. Through our work, we have gained in-depth experience across a diverse range of over a dozen drivers. This breadth of experience enables us to design, implement, and optimize drivers that are tailored to the unique requirements of your enterprise.\nOur solutions are built to ensure seamless integration, robust security, and efficient identity and access management across your systems. We are committed to delivering high-quality driver development services that align with your organizational goals and enhance operational efficiency.\nThe first Connector that Belkast was tasked with developing was a JDBC Connector. This assignment was particularly advantageous, as one of our team members possessed a background as a Database Administrator. His expertise extended beyond database scripting to include the ability to diagnose and resolve potential issues efficiently.\nConnector development frequently encompasses more than just the Identity Management component. It often requires extensive involvement in the configuration of the target application as well. As a result, developers are regularly called upon to assume dual roles: they must not only focus on the technical aspects of Identity Management but also ensure that the application itself is correctly configured to integrate seamlessly with the Connector.\nThis dual responsibility demands a versatile skill set, enabling developers to navigate both domains effectively:\nNeeds to know how the Identity Manager Connector works Needs to have some knowledge of how the application works Belkast has experience with over a dozen IDM Connectors:\nActive Directory\nAzure AD\nDelimited Text (CSV)\neDirectory\nGeneric File\nGoogle Apps\nJDBC\nJMS\nLDAP\nLoopback and NULL\nMicrosoft Office 365\nREST\nScripting\nSOAP\nWorkOrder\n","date":"12 November 2020","externalUrl":null,"permalink":"/connectors/","section":"Posts","summary":"Our primary focus lies in the development of NetIQ Identity Manager drivers, an area in which we have cultivated extensive expertise over the years. Through our work, we have gained in-depth experience across a diverse range of over a dozen drivers. This breadth of experience enables us to design, implement, and optimize drivers that are tailored to the unique requirements of your enterprise.\n","title":"Connector Development","type":"posts"},{"content":"Are you encountering challenges with complex or convoluted code that is impacting your deployment process? If you require a comprehensive deployment review, we are here to assist.\nOur team specializes in analyzing and optimizing codebases to ensure clarity, efficiency, and reliability. Whether you are seeking to streamline your deployment workflows, identify potential issues, or enhance overall system performance, we provide the expertise and support necessary to achieve your objectives. Please do not hesitate to reach out for assistance.\n","date":"12 November 2020","externalUrl":null,"permalink":"/review/","section":"Posts","summary":"Are you encountering challenges with complex or convoluted code that is impacting your deployment process? If you require a comprehensive deployment review, we are here to assist.\nOur team specializes in analyzing and optimizing codebases to ensure clarity, efficiency, and reliability. Whether you are seeking to streamline your deployment workflows, identify potential issues, or enhance overall system performance, we provide the expertise and support necessary to achieve your objectives. Please do not hesitate to reach out for assistance.\n","title":"Deployment review","type":"posts"},{"content":"Our core competency lies in delivering specialized services for NetIQ Identity Applications. With a focus on this critical area, we provide tailored solutions that address the unique identity and access management needs of enterprises.\nA key strength of our offering is workflow development, an area in which we possess over a decade of experience. Our expertise encompasses the design, implementation, and optimization of workflows that streamline identity provisioning, access requests, approval processes, and compliance management. We ensure that these workflows are not only efficient and secure but also aligned with your organizational policies and business objectives.\nOur deep understanding of NetIQ Identity Applications enables us to deliver solutions that enhance operational efficiency, reduce manual intervention, and improve overall governance. We are committed to supporting your organization in achieving seamless and effective identity and access management.\nWhether you require a portal for White Pages functionality or a more complex solution utilizing Workflow management, Belkast has the expertise to deliver both.\nFor the past fifteen years, Belkast has developed Workflows at multiple clients, starting with NetIQ Identity Management version 4.0.2.\nUnion Pacific # We development multiple workflows for Union Pacific staff.\nING Insurance # We developed a set of Workflows to facilitate the management of Secondary User Accounts.\nAllied Irish Banks # We replaced the legacy, and unsupported, eGuide product with a new internal employee search portal called People Finder.\nNorthern Ireland Education Board # We developed a password reset page for staff that communicated directly with the NMAS API to perform the password change. The User was informed directly on the webpage whether or not the password reset was successful. The communication with the NMAS API was made possible via a web applet that was built by Belkast Consulting.\nMore details are available here.\n","date":"12 November 2020","externalUrl":null,"permalink":"/workflows/","section":"Posts","summary":"Our core competency lies in delivering specialized services for NetIQ Identity Applications. With a focus on this critical area, we provide tailored solutions that address the unique identity and access management needs of enterprises.\n","title":"Workflow Development","type":"posts"},{"content":"It is sometimes useful to be able to compare two arrays before loading the result into an MVEditor or a list. A few scenarios of when this is useful are:\nWhen you want to determine which Groups to display to a User on a Workflow form, knowing that some Groups are restricted and should not be displayed When you want to know the value(s) which the User has modified in a list of items. An item may have been added or removed by the User When you want to display text to the User, keeping track of additions and deletions to and from a list The JavaScript / ECMAScript code is show below.\nfunction RemoveArrayItems(itemsToRemove,array) { if (array.length == 0 || itemsToRemove.length==0) return array; var sMatchedItems = \u0026#34;|\u0026#34; + itemsToRemove.join(\u0026#39;|\u0026#39;) + \u0026#34;|\u0026#34;; var newArray=[]; for (var i = 0;i \u0026lt; array.length;i++) { if (sMatchedItems.indexOf(\u0026#34;|\u0026#34; + array[i] + \u0026#34;|\u0026#34;) \u0026lt; 0) { newArray[newArray.length] = array[i]; } } if (newArray.length \u0026gt; 0) { form.setValues(\u0026#34;fldGroupChange\u0026#34;, \u0026#34;TRUE\u0026#34;); } return newArray; } Given the function listed above, it can be used as shown in the example below. var newArray = form.getValues(\u0026#34;fldGroup\u0026#34;); var oldArray = form.getValues(\u0026#34;fldOldGroup\u0026#34;); var newItemsAdded = RemoveArrayItems(oldArray,newArray); var ItemsRemoved = RemoveArrayItems(newArray,oldArray); form.setValues(\u0026#34;fldAddedGroup\u0026#34;, newItemsAdded); form.setValues(\u0026#34;fldDeletedGroup\u0026#34;, ItemsRemoved); The fldGroup field is where the current Groups assigned to a User are populated. The fldOldGroup field is a copy of the Groups, and is populated so I can keep track of the added and removed Groups. When the logged in User modifies the Groups assigned to a User, I compare oldArray and newArray (to get the added) and also newArray and oldArray (to get the Deleted). This makes it very easy to run a loop around the added Array and the deleted Array and only manipulate that data on the User. This is better coding, in my opinion, than always replacing the values regardless of whether they have changed or not.\n","date":"12 May 2018","externalUrl":null,"permalink":"/series/ecmascript/compare-two-arrays/","section":"Series","summary":"It is sometimes useful to be able to compare two arrays before loading the result into an MVEditor or a list. A few scenarios of when this is useful are:\n","title":"Array Comparison","type":"series"},{"content":"The ECMAScript code shown here SHA-256 hashes a string (sample output shown below). hash: e52a26fe5ef07876b5e7f57c907a5a033cb60fc6eb9ffc462748b9c9aeeb45f5\nimportPackage(java.io); importPackage(Packages.java.lang); importClass(java.security.MessageDigest); importClass(java.nio.charset.StandardCharsets); importClass(java.lang.StringBuffer); var varMyString = \u0026#34;Hash this string please!\u0026#34;; var varDigest = MessageDigest.getInstance(\u0026#34;SHA-256\u0026#34;); var varOriginalString = new java.lang.String(varMyString); var varHashedString = varDigest.digest(varOriginalString.getBytes(StandardCharsets.UTF_8)); var varFinalString = new StringBuffer(); for (i = 0; i \u0026lt; varHashedString.length; i++) { varFinalString.append(Integer.toString((varHashedString[i] \u0026amp; 0xff) + 0x100, 16).substring(1)); } print (\u0026#34;hash: \u0026#34; + varFinalString); ","date":"12 May 2018","externalUrl":null,"permalink":"/series/ecmascript/string-hasher/","section":"Series","summary":"The ECMAScript code shown here SHA-256 hashes a string (sample output shown below). hash: e52a26fe5ef07876b5e7f57c907a5a033cb60fc6eb9ffc462748b9c9aeeb45f5\nimportPackage(java.io); importPackage(Packages.java.lang); importClass(java.security.MessageDigest); importClass(java.nio.charset.StandardCharsets); importClass(java.lang.StringBuffer); var varMyString = \"Hash this string please!\"; var varDigest = MessageDigest.getInstance(\"SHA-256\"); var varOriginalString = new java.lang.String(varMyString); var varHashedString = varDigest.digest(varOriginalString.getBytes(StandardCharsets.UTF_8)); var varFinalString = new StringBuffer(); for (i = 0; i \u003c varHashedString.length; i++) { varFinalString.append(Integer.toString((varHashedString[i] \u0026 0xff) + 0x100, 16).substring(1)); } print (\"hash: \" + varFinalString);","title":"Hash a string","type":"series"},{"content":"The JavaScript / ECMAScript code shown below was used in a recent project in order to generate a Unique Identifier when a User created a new Service Provider definition from within a User Application Workflow. When the Service Provider feed was consumed by the JMS Connector, the Connector checked the metadata present in the Identity Vault against the UUID sent as part of the XML file ‘header data’. If the two UUID values matched, the Service Provider long name was equal, and the Service Provider short name was equal, the XML file was accepted; otherwise the XML file was rejected.\nprint ( \u0026#34;UUID: \u0026#34; + generateUUID() ); function generateUUID() { var s = []; var varFinalString = []; var varMyString = []; var hexDigits = \u0026#34;0123456789abcdef\u0026#34;; for (var i = 0; i \u0026lt; 32; i++) { s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); } s[12] = \u0026#34;4\u0026#34;; // bits 12-15 of the time_hi_and_version field to 0010 s[16] = hexDigits.substr((s[16] \u0026amp; 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01 var uuid = s.join(\u0026#34;\u0026#34;); var varMyString1 = uuid.substring(0,8); var varMyString2 = uuid.substring(8,12); var varMyString3 = uuid.substring(12,16); var varMyString4 = uuid.substring(16,20); var varMyString5 = uuid.substring(20,32); var varMyReturnString = (varMyString1 + \u0026#39;-\u0026#39; + varMyString2 + \u0026#39;-\u0026#39; + varMyString3 + \u0026#39;-\u0026#39; + varMyString4 + \u0026#39;-\u0026#39; + varMyString5); return varMyReturnString; } Two examples are shown below:\nUUID: e4e37411-960e-4c40-8b72-0d39a8c4ee61 UUID: be221719-c3aa-4a44-8288-9a891e96d7dd ","date":"12 May 2018","externalUrl":null,"permalink":"/series/ecmascript/uuid/","section":"Series","summary":"The JavaScript / ECMAScript code shown below was used in a recent project in order to generate a Unique Identifier when a User created a new Service Provider definition from within a User Application Workflow. When the Service Provider feed was consumed by the JMS Connector, the Connector checked the metadata present in the Identity Vault against the UUID sent as part of the XML file ‘header data’. If the two UUID values matched, the Service Provider long name was equal, and the Service Provider short name was equal, the XML file was accepted; otherwise the XML file was rejected.\n","title":"UUID Generator","type":"series"},{"content":"Belkast was recently asked to implement an XSLT stylesheet to be used within a NetIQ IDM Connector. Rather than start up a Virtual Machine (VM), we decided to take a different approach: test the XSLT from the command line. Fortunately, it’s not very difficult to do this by using the built in XSLT processor in Java.\nUsing java com.sun.org.apache.xalan.internal.xsltc.cmdline with Compile and Transform, one can easily convert an XML file from one format to another format using an XSLT stylesheet.\nShown below is a BASH script that allows one to test this out on a Linux machine!\n#!/bin/bash set -e XSL=`readlink -f \u0026#34;$1\u0026#34;` XML=`readlink -f \u0026#34;$2\u0026#34;` shift ; shift TMPDIR=\u0026#34;\u0026#34; cleanup () { set +e popd \u0026gt; /dev/null [[ \u0026#34;$TMPDIR\u0026#34; ]] \u0026amp;\u0026amp; rm -rf \u0026#34;$TMPDIR\u0026#34; } TMPDIR=`mktemp -d` pushd \u0026#34;$TMPDIR\u0026#34; \u0026gt; /dev/null trap cleanup EXIT if [ -f \u0026#34;$XSL\u0026#34; ]; then cp \u0026#34;$XSL\u0026#34; mytran.xsl java com.sun.org.apache.xalan.internal.xsltc.cmdline.Compile mytran.xsl else echo stylesheet does not exist exit 1 fi if [ -f \u0026#34;$XML\u0026#34; ]; then java com.sun.org.apache.xalan.internal.xsltc.cmdline.Transform \u0026#34;$XML\u0026#34; mytran \u0026#34;$@\u0026#34; else echo no file to transform exit 1 fi ","date":"12 May 2018","externalUrl":null,"permalink":"/posts/code/xslt-tester/","section":"Posts","summary":"Belkast was recently asked to implement an XSLT stylesheet to be used within a NetIQ IDM Connector. Rather than start up a Virtual Machine (VM), we decided to take a different approach: test the XSLT from the command line. Fortunately, it’s not very difficult to do this by using the built in XSLT processor in Java.\n","title":"XSLT Tester","type":"posts"},{"content":"Below you will find the JavaScript / ECMAScript code which will convert JD Edwards Julian Dates to Java dates. The output format can be tweaked to your liking.\n// According to the Oracle doc for JDE it’s C + YY + DDD // C = Century 0 = 19, 1 = 20 // 097065 1997 and the 65th day of the year. // 071138 138th day of 1971 = MM 05 / DD 19 / 1971 importClass(java.text.SimpleDateFormat); var varMyDate = \u0026#34;71069\u0026#34;; print (\u0026#34;JDE Date: \u0026#34; + varMyDate); var varParsedDate = parseString(varMyDate); print (\u0026#34;Out Date: \u0026#34; + varParsedDate); function parseString(varString) { if (varString.length == 5) { varString = 0 + varString; } if (varString.length != 6) return; var varCentury = varString.substring(0,1); var varYear = varString.substring(1,3); var varDay = varString.substring(3,6); varYear = +varCentury + +19 + varYear; // print (varCentury); // print (varYear); // print (varDay); return dateFromDay(varYear, varDay); } function dateFromDay(year, day) { pattern = \u0026#34;yyyyMMdd\u0026#34;; format = new SimpleDateFormat(pattern); var varDate = new Date(year, 0); var varTempDate = new Date(varDate.setDate(day)); return format.format(varDate); } The code handles leap year conversion, as the following examples show.\n","date":"10 April 2017","externalUrl":null,"permalink":"/series/ecmascript/julian-date-conversion/","section":"Series","summary":"Below you will find the JavaScript / ECMAScript code which will convert JD Edwards Julian Dates to Java dates. The output format can be tweaked to your liking.\n// According to the Oracle doc for JDE it’s C + YY + DDD // C = Century 0 = 19, 1 = 20 // 097065 1997 and the 65th day of the year. // 071138 138th day of 1971 = MM 05 / DD 19 / 1971 importClass(java.text.SimpleDateFormat); var varMyDate = \"71069\"; print (\"JDE Date: \" + varMyDate); var varParsedDate = parseString(varMyDate); print (\"Out Date: \" + varParsedDate); function parseString(varString) { if (varString.length == 5) { varString = 0 + varString; } if (varString.length != 6) return; var varCentury = varString.substring(0,1); var varYear = varString.substring(1,3); var varDay = varString.substring(3,6); varYear = +varCentury + +19 + varYear; // print (varCentury); // print (varYear); // print (varDay); return dateFromDay(varYear, varDay); } function dateFromDay(year, day) { pattern = \"yyyyMMdd\"; format = new SimpleDateFormat(pattern); var varDate = new Date(year, 0); var varTempDate = new Date(varDate.setDate(day)); return format.format(varDate); } The code handles leap year conversion, as the following examples show.\n","title":"JDE Julian Date Conversion","type":"series"},{"content":"This article describes how one can keep track of comments which have been entered into one or more forms during the NetIQ User Application request process. This is helpful if you want to display a ‘running commentary’ of the previously entered comments on each new form when it is presented to the User.\nImplementation For instance, during the request process, the business logic might require that a User enters some comments detailing why the request was made. When the request is submitted, there may well be a two-step approval with an approval form being routed to the Manager of the requestor and another approval form being routed to the Service Desk group which, in turn, requires two approvals. Assume we have the following actors in the request process:\nThe requestor is User A The Manager of the requestor is User B The 1st Service Desk User is User X The 2nd Service Desk User is User Y By using the code contained within this article, you can expect the following outcome:\nUser A submits a request via a form which includes a mandatory comments field User B opens the approval form and sees the comments which were entered in by User A. User B approves the request User X opens the Service Desk approval form and sees the comments entered in by both User A and User B. User X approves the request User Y opens the Service Desk approval form and sees the comments entered in by User A, User B, and User X. User Y denies the request Since the request process has finished (User Y denied the request), an email is sent to User A and User B Within the email, there is a HTML table which displays all the comments entered during the request life-cycle (including their own). User A and User B would be able to see that the request was denied, the time the request was denied, by whom the request was denied, and the reason for the denial In order to implement such a solution, the first thing that is required is to add a data-mapping activity after each of the forms for which you want to collect a comment. The source for the data mapping is shown below. The result of the data-mapping is stored in the flowdata variable Global/values/FinalComments. In the code below, the form where the comments have been captured is Activity15.\nfunction returnMe() { var varCommentsArray = new Packages.java.util.ArrayList(); var varComments = new Packages.java.util.ArrayList(); var varDelimiter = flowdata.get(\u0026#39;Global/values/ProcessID\u0026#39;); var varDate = new Date(); var varActorType = \u0026#39;Manager\u0026#39; var varAction = \u0026#39;APPROVED\u0026#39; varCommentsArray = flowdata.getObject(\u0026#39;Global/values/FinalComments\u0026#39;); var varTimeStamp = varDate.toString(); var varActorName = IDVault.get(\u0026#39;Activity15.getAddressee().toString()\u0026#39;, \u0026#39;Employee\u0026#39;, \u0026#39;FullName\u0026#39;); var varDetails = flowdata.get(\u0026#39;Activity15/approval_form/fldFormComments\u0026#39;).toString(); varComments = returnComments(varActorType, varAction, varActorName, varDetails, varTimeStamp, varDelimiter, varCommentsArray); return varComments; } returnMe(); The returnComments function, called from the above function, is shown below.\nfunction returnComments(varActorType, varAction, varActorName, varDetails, varTimeStamp, varDelimiter, varCommentsArray) { var varDate = new Date(); var varTimeStamp = varDate.toString(); var varComponents = varTimeStamp + varDelimiter + varActorType + varDelimiter + varAction + varDelimiter + varActorName + varDelimiter + varDetails; varCommentsArray.add(varComponents); return varCommentsArray; } The code below is used to display the comments in a HTML field. The code can be used either in a pre data-mapping activity for a HTML field on a form, or as part of the HTML email template. Because there is only a single function to construct the table, the displayed data on the form and in the email looks identical, colours and all.\nfunction buildCommentsTable(varCommentsArray, varCommentsArraySize, varHtmlStart, varHtmlEnd, varDelimiter) { var varString = varHtmlStart; for (i=0;i \u0026lt; varCommentsArraySize;i++) { var varComponent = varCommentsArray.get(i).getFirstChild().getNodeValue(); varComponentArray = varComponent.split(varDelimiter); varString = varString + \u0026#34;\u0026lt;tr\u0026gt;\u0026#34;; for (j=0;j \u0026lt; varComponentArray.length;j++) { varString = varString + \u0026#34;\u0026lt;td\u0026gt;\u0026#34;; varString = varString + varComponentArray[j].toString(); varString = varString + \u0026#34;\u0026lt;/td\u0026gt;\u0026#34;; } varString = varString + \u0026#34;\u0026lt;/tr\u0026gt;\u0026#34;; } varString = varString + varHtmlEnd; return varString; } ","date":"11 August 2016","externalUrl":null,"permalink":"/posts/code/idm/workflows/track-comments/","section":"Posts","summary":"This article describes how one can keep track of comments which have been entered into one or more forms during the NetIQ User Application request process. This is helpful if you want to display a ‘running commentary’ of the previously entered comments on each new form when it is presented to the User.\n","title":"Track Workflow Comments","type":"posts"},{"content":"The following stylesheet processes each \u0026lt;status\u0026gt; node present within an XDS document, and tags each node with the position of that node. For example, if the XDS document contains 3 \u0026lt;status\u0026gt; nodes, the first node will be rewritten as \u0026lt;status position=”0″\u0026gt; etc. The code listed below makes use of the XSLT function preceding-sibling in order to figure out at what position the node is placed within the context of the XDS document. The code loops around each node, one after the other, and tags each node with the relative position.\n\u0026lt;xsl:stylesheet exclude-result-prefixes=\u0026#34;query cmd dncv\u0026#34; version=\u0026#34;1.0\u0026#34; xmlns:xsl=\u0026#34;http://www.w3.org/1999/XSL/Transform\u0026#34;\u0026gt; \u0026lt;xsl:param name=\u0026#34;srcQueryProcessor\u0026#34;/\u0026gt; \u0026lt;xsl:param name=\u0026#34;destQueryProcessor\u0026#34;/\u0026gt; \u0026lt;xsl:param name=\u0026#34;srcCommandProcessor\u0026#34;/\u0026gt; \u0026lt;xsl:param name=\u0026#34;destCommandProcessor\u0026#34;/\u0026gt; \u0026lt;xsl:param name=\u0026#34;dnConverter\u0026#34;/\u0026gt; \u0026lt;xsl:param name=\u0026#34;fromNds\u0026#34;/\u0026gt; \u0026lt;xsl:template match=\u0026#34;output/status\u0026#34;\u0026gt; \u0026lt;xsl:variable name=\u0026#34;varPositionValue\u0026#34; select=\u0026#34;count(preceding-sibling::*/operation-data/*)\u0026#34;/\u0026gt; \u0026lt;xsl:message\u0026gt;[Status] Adding in position value of \u0026#39;\u0026lt;xsl:value-of select=\u0026#34;$varPositionValue\u0026#34;/\u0026gt;\u0026#39;\u0026lt;/xsl:message\u0026gt; \u0026lt;xsl:copy\u0026gt; \u0026lt;xsl:attribute name=\u0026#34;position\u0026#34;\u0026gt; \u0026lt;xsl:value-of select=\u0026#34;$varPositionValue\u0026#34;/\u0026gt; \u0026lt;/xsl:attribute\u0026gt; \u0026lt;xsl:apply-templates select=\u0026#34;@*|node()\u0026#34;/\u0026gt; \u0026lt;/xsl:copy\u0026gt; \u0026lt;/xsl:template\u0026gt; \u0026lt;xsl:template match=\u0026#34;node()|@*\u0026#34;\u0026gt; \u0026lt;xsl:copy\u0026gt; \u0026lt;xsl:apply-templates select=\u0026#34;@*|node()\u0026#34;/\u0026gt; \u0026lt;/xsl:copy\u0026gt; \u0026lt;/xsl:template\u0026gt; \u0026lt;/xsl:stylesheet\u0026gt; You can use the code below to ensure that multiple email notifications are not sent out when processing an XDS document with multiple \u0026lt;status\u0026gt; tags.\n\u0026lt;conditions\u0026gt; \u0026lt;and\u0026gt; \u0026lt;if-operation op=\u0026#34;equal\u0026#34;\u0026gt;status\u0026lt;/if-operation\u0026gt; \u0026lt;if-xpath op=\u0026#34;true\u0026#34;\u0026gt;@level=\u0026#39;success\u0026#39;\u0026lt;/if-xpath\u0026gt; \u0026lt;if-xpath op=\u0026#34;true\u0026#34;\u0026gt;@position=\u0026#39;0\u0026#39;\u0026lt;/if-xpath\u0026gt; \u0026lt;if-global-variable name=\u0026#34;gcvAudit\u0026#34; op=\u0026#34;equal\u0026#34;\u0026gt;true\u0026lt;/if-global-variable\u0026gt; \u0026lt;if-local-variable mode=\u0026#34;regex\u0026#34; name=\u0026#34;varUserEmailAddress\u0026#34; op=\u0026#34;equal\u0026#34;\u0026gt;^.+$\u0026lt;/if-local-variable\u0026gt; \u0026lt;/and\u0026gt; \u0026lt;/conditions\u0026gt; ","date":"2 June 2016","externalUrl":null,"permalink":"/posts/code/tag-node-position/","section":"Posts","summary":"The following stylesheet processes each \u003cstatus\u003e node present within an XDS document, and tags each node with the position of that node. For example, if the XDS document contains 3 \u003cstatus\u003e nodes, the first node will be rewritten as \u003cstatus position=”0″\u003e etc. The code listed below makes use of the XSLT function preceding-sibling in order to figure out at what position the node is placed within the context of the XDS document. The code loops around each node, one after the other, and tags each node with the relative position.\n","title":"Tag node position using XSLT","type":"posts"},{"content":" If you want to reuse one of the scripts on this page, please ensure that you also have the following programs installed. i3 or i3-gaps dmenu rofi dunstify kitty zsh mpc putty qutebrowser xdotool warp feh expressvpn pcmanfm pyradio arecord aplay The necessary fonts dmenu / rofi Select an SSH session (requires putty) In your i3-gaps ( or i3 ) configuration file, put the following line:\nbindsym $alt+Ctrl+s exec ~/scripts/putty.sh #!/bin/bash OLD_IFS=\u0026#34;$IFS\u0026#34; IFS=$\u0026#39;\\n\u0026#39; win_array=($(ls --ignore=\u0026#34;Default*\u0026#34; ~/.config/putty/sessions)) IFS=\u0026#34;$OLD_IFS\u0026#34; win_length=${#win_array[@]} # echo $win_length for i in \u0026#34;${!win_array[@]}\u0026#34;; do win_array[$i]=${win_array[$i]//\u0026#34;%20\u0026#34;/\u0026#34; \u0026#34;} win_array[$i]=${win_array[$i]//\u0026#34;%28\u0026#34;/\u0026#34;(\u0026#34;} win_array[$i]=${win_array[$i]//\u0026#34;%29\u0026#34;/\u0026#34;)\u0026#34;} done # for i in \u0026#34;${!win_array[@]}\u0026#34;; do # echo ${win_array[${i}]}; # done choice=$(printf \u0026#39;%s\\n\u0026#39; \u0026#34;${win_array[@]}\u0026#34; | rofi -dmenu -i -p \u0026#34;Putty Session\u0026#34;) \u0026#34;$@\u0026#34; || exit echo $choice putty -load \u0026#34;$choice\u0026#34; exit 0 Select a Web search engine In your i3-gaps ( or i3 ) configuration file, put the following line:\nbindsym $alt+Ctrl+slash exec ~/scripts/search.sh #!/usr/bin/env bash # # Script name: dmsearch # Description: Search various search engines (inspired by surfraw). # Dependencies: dmenu and a web browser # GitLab: https://www.gitlab.com/dwt1/dmscripts # Contributors: Derek Taylor # Defining our web browser. BROWSER=\u0026#34;qutebrowser\u0026#34; # An array of search engines. You can edit this list to add/remove # search engines. The format must be: \u0026#34;engine_name - url\u0026#34;. # The url format must allow for the search keywords at the end of the url. # For example: https://www.amazon.com/s?k=XXXX searches Amazon for \u0026#39;XXXX\u0026#39;. declare -a options=( \u0026#34;startpage - https://www.startpage.com/sp/search?query=\u0026#34; \u0026#34;amazon - https://www.amazon.com/s?k=\u0026#34; \u0026#34;archpkg - https://archlinux.org/packages/?sort=\u0026amp;q=\u0026#34; \u0026#34;archwiki - https://wiki.archlinux.org/index.php?search=\u0026#34; \u0026#34;bbcnews - https://www.bbc.co.uk/search?q=\u0026#34; \u0026#34;bing - https://www.bing.com/search?q=\u0026#34; \u0026#34;cnn - https://www.cnn.com/search?q=\u0026#34; \u0026#34;ebay - https://www.ebay.com/sch/i.html?\u0026amp;_nkw=\u0026#34; \u0026#34;github - https://github.com/search?q=\u0026#34; \u0026#34;gitlab - https://gitlab.com/search?search=\u0026#34; \u0026#34;google - https://www.google.com/search?q=\u0026#34; \u0026#34;reddit - https://www.reddit.com/search/?q=\u0026#34; \u0026#34;slashdot - https://slashdot.org/index2.pl?fhfilter=\u0026#34; \u0026#34;stockquote - https://finance.yahoo.com/quote/\u0026#34; \u0026#34;thesaurus - https://www.thesaurus.com/misspelling?term=\u0026#34; \u0026#34;translate - https://translate.google.com/?sl=auto\u0026amp;tl=en\u0026amp;text=\u0026#34; \u0026#34;webster - https://www.merriam-webster.com/dictionary/\u0026#34; \u0026#34;wikipedia - https://en.wikipedia.org/wiki/\u0026#34; \u0026#34;youtube - https://www.youtube.com/results?search_query=\u0026#34; ) # Picking a search engine. while [ -z \u0026#34;$engine\u0026#34; ]; do enginelist=$(printf \u0026#39;%s\\n\u0026#39; \u0026#34;${options[@]}\u0026#34; | rofi -dmenu -i -p \u0026#39;Search Engine\u0026#39;) || exit engineurl=$(echo \u0026#34;$enginelist\u0026#34; | awk \u0026#39;{print $NF}\u0026#39;) engine=$(echo \u0026#34;$enginelist\u0026#34; | awk \u0026#39;{print $1}\u0026#39;) done # Searching the chosen engine. while [ -z \u0026#34;$query\u0026#34; ]; do query=$(echo -e \u0026#34;\u0026#34; | rofi -dmenu -i -p \u0026#34;Search For\u0026#34;) || exit done # Display search results in web browser $BROWSER \u0026#34;$engineurl\u0026#34;\u0026#34;$query\u0026#34; Switch to a directory (requires kitty, zsh, warp, pcmanf) In your i3-gaps ( or i3 ) configuration file, put the following line:\nbindsym $alt+Ctrl+period exec /home/karmst/scripts/warp.sh #!/bin/bash d_program() { if [[ $cmd != \u0026#39;\u0026#39; ]]; then { printf \u0026#39;%s\\n\u0026#39; \u0026#34;terminal\u0026#34; printf \u0026#39;%s\\n\u0026#39; \u0026#34;pcmanfm\u0026#34; } | dmenu -l 20 -c -b -fn \u0026#39;SauceCodePro Nerd Font-16\u0026#39; -sb \u0026#34;#0f61a9\u0026#34; -h 30 -bw 3 -i -p \u0026#39;\u0026#39; ${dmenu_args[@]} fi } shell_window=\u0026#34;zsh\u0026#34; my_term=\u0026#34;kitty\u0026#34; post_term=\u0026#34;--directory\u0026#34; window=$(xdotool search --name $shell_window) cmd=$(cat ~/.warprc | sed \u0026#39;s/\\(.\\+\\)\\(:\\)\\(.\\+\\)/\\1/\u0026#39; | sort | rofi -dmenu -sort -l 15 -sb \u0026#34;#0f61a9\u0026#34; -fn \u0026#39;SauceCodePro Nerd Font-16\u0026#39; -c -i -p \u0026#34;Directory\u0026#34;) if [ -n $cmd ]; then url=$(grep \u0026#34;$cmd:\u0026#34; ~/.warprc | sed \u0026#39;s/\\(.\\+\\)\\(:\\)\\(.\\+\\)/\\3/\u0026#39;) program=$(d_program) if [[ ($window != \u0026#39;\u0026#39;) \u0026amp;\u0026amp; ($program == \u0026#39;terminal\u0026#39;) ]]; then i3-msg \u0026#39;[title=$shell_window] focus\u0026#39; sleep 0.25 xdotool search --name $shell_window key ctrl+u sleep 0.25 xdotool search --name $shell_window key BackSpace xdotool search --name $shell_window type cd \u0026#34; $url\u0026#34; xdotool search --name $shell_window key KP_Enter xdotool search --name $shell_window type dirt xdotool search --name $shell_window key KP_Enter fi if [[ ($window == \u0026#39;\u0026#39;) \u0026amp;\u0026amp; ($program == \u0026#39;terminal\u0026#39;) ]]; then $my_term $post_term $url sleep 2 xdotool search --name $shell_window type dirt xdotool search --name $shell_window key KP_Enter fi if [[ $program == \u0026#39;pcmanfm\u0026#39; ]]; then pcmanfm -n --role=\u0026#34;PCManFM\u0026#34; $url fi fi exit 0 Open a Web Bookmark (requires qutebrowser) In your i3-gaps ( or i3 ) configuration file, put the following line:\nbindsym $alt+Ctrl+slash exec ~/scripts/bmarks.sh\u003c/li\u003e #!/usr/bin/env bash # # Based on a script by Derek Taylor. # GitLab: https://www.gitlab.com/dwt1/dmscripts # # The script has been greatly expanded upon! BROWSER=qutebrowser #BROWSER=librewolf MYFILE=\u0026#34;$HOME/.config/qutebrowser/personal\u0026#34; # Defining location of bookmarks file BMFILE=\u0026#34;$HOME/.config/qutebrowser/bookmarks/urls\u0026#34; # Defining location of quickmarks file QMFILE=\u0026#34;$HOME/.config/qutebrowser/quickmarks\u0026#34; # Defining location of history database HISTDB=\u0026#34;$HOME/.local/share/qutebrowser/history.sqlite\u0026#34; # A separator that will appear in between quickmarks, bookmarks and history urls. SEPARATOR=\u0026#34;----------\u0026#34; # Read array of options to choose. readarray -t bmarks \u0026lt; \u0026#34;$BMFILE\u0026#34; readarray -t qmarks \u0026lt; \u0026#34;$QMFILE\u0026#34; readarray -t mymarks \u0026lt; \u0026#34;$MYFILE\u0026#34; # Sort the bookmark, quickmark and history lists so that the url is the last field. # We will awk print the last field later. # History list is formed by grep\u0026#39;ing \u0026#34;http\u0026#34; from the history table. mylist=$(printf \u0026#39;%s\\n\u0026#39; \u0026#34;${mymarks[@]}\u0026#34; | awk \u0026#39;{out=\u0026#34;\u0026#34;; for(i=1;i \u0026lt; NF;i++){out=out\u0026#34; \u0026#34;$i}; print \u0026#34;[P]\u0026#34; out \u0026#34;  \u0026#34; $NF}\u0026#39; | sort | uniq) #bmlist=$(printf \u0026#39;%s\\n\u0026#39; \u0026#34;${bmarks[@]}\u0026#34; | awk \u0026#39;{print $2 \u0026#34; \u0026#34; $1}\u0026#39; OFS=\u0026#34;(^.+?) \u0026#34; | sort | uniq) bmlist=$(printf \u0026#39;%s\\n\u0026#39; \u0026#34;${bmarks[@]}\u0026#34; | awk \u0026#39;{out=$2; for(i=3;i\u0026lt;=NF;i++){out=out\u0026#34; \u0026#34;$i}; print \u0026#34;[B] \u0026#34; out \u0026#34;  \u0026#34; $1}\u0026#39; | sort | uniq) # qmlist=$(printf \u0026#39;%s\\n\u0026#39; \u0026#34;${qmarks[@]}\u0026#34; | awk \u0026#39;{print \u0026#34;[Q] \u0026#34;$1 \u0026#34; -\u0026gt; \u0026#34;$NF}\u0026#39; | sort | uniq) qmlist=$(printf \u0026#39;%s\\n\u0026#39; \u0026#34;${qmarks[@]}\u0026#34; | awk \u0026#39;{out=\u0026#34;\u0026#34;; for(i=1;i \u0026lt; NF;i++){out=out\u0026#34; \u0026#34;$i}; print \u0026#34;[Q]\u0026#34; out \u0026#34;  \u0026#34; $NF}\u0026#39; | sort | uniq) SQL=\u0026#34;SELECT h.title, h.url FROM history as h where url like \u0026#39;http%\u0026#39; ORDER BY h.atime DESC;\u0026#34; histlist=$(printf \u0026#39;%s\\n\u0026#39; \u0026#34;$(sqlite3 \u0026#34;$HISTDB\u0026#34; \u0026#34;${SQL}\u0026#34;)\u0026#34; | awk -F \u0026#34;|\u0026#34; \u0026#39;{print \u0026#34;[H] \u0026#34;$1\u0026#34;  \u0026#34;$NF}\u0026#39; | sort | uniq) # Piping the above lists into dmenu. # We use \u0026#34;printf \u0026#39;%s\\n\u0026#39;\u0026#34; to format the array one item to a line. # The urls are listed quickmarks first, then the SEPARATOR, and then bookmarks. choice=$(printf \u0026#39;%s\\n\u0026#39; \u0026#34;$SEPARATOR\u0026#34; \u0026#34;$mylist\u0026#34; \u0026#34;$SEPARATOR\u0026#34; \u0026#34;$qmlist\u0026#34; \u0026#34;$SEPARATOR\u0026#34; \u0026#34;$bmlist\u0026#34; \u0026#34;$SEPARATOR\u0026#34; \u0026#34;$histlist\u0026#34; \u0026#34;$SEPARATOR\u0026#34; | rofi -dmenu -i -l 25 -p \u0026#34;Open URL ($BROWSER)\u0026#34;) \u0026#34;$@\u0026#34; || exit # What to do if the separator is chosen from the list. # We simply launch qutebrowser without any url arguments. # shellcheck disable=SC2154 if [ \u0026#34;$choice\u0026#34; == \u0026#34;$SEPARATOR\u0026#34; ]; then $BROWSER # What to do when/if we choose a url to view. elif [ \u0026#34;$choice\u0026#34; ]; then url=$(echo \u0026#34;${choice}\u0026#34; | awk \u0026#39;{print $NF}\u0026#39;) || exit $BROWSER \u0026#34;$url\u0026#34; # What to do if we just escape without choosing anything. else echo \u0026#34;Program terminated.\u0026#34; \u0026amp;\u0026amp; exit 0 fi Music (requires mpc) Play Songs In your i3-gaps ( or i3 ) configuration file, put the following line:\nbindsym $alt+Ctrl+m exec ~/scripts/rofi-random.sh #!/bin/bash selection=$(echo -e \u0026#34;clear\\nkiller\\nrandom\\nquery\\nnewsboat\\nmusic_player\\nradio_player\u0026#34; | dmenu -c -l 7 -b -bw 3 -fn \u0026#39;Droid Sans Mono-16\u0026#39; -sb \u0026#34;#0f61a9\u0026#34; -i -p \u0026#34;\u0026#34;) case $selection in clear) ($HOME/scripts/play_music.sh c) ;; random) ( random=$(echo -e \u0026#34;\u0026#34; | dmenu -c -b -fn \u0026#39;Droid Sans Mono-16\u0026#39; -bw 3 -sb \u0026#34;#0f61a9\u0026#34; -i -p \u0026#34;\u0026#34;) $HOME/scripts/play_music.sh $random songs=`mpc playlist` dunstify -t 10000 \u0026#34;$songs\u0026#34; ) ;; query) ( search=$(echo -e \u0026#34;\u0026#34; | dmenu -c -b -bw 3 -fn \u0026#39;Droid Sans Mono-16\u0026#39; -sb \u0026#34;#0f61a9\u0026#34; -i -p \u0026#34;\u0026#34;) $HOME/scripts/play_music.sh q $search songs=`mpc playlist` dunstify -t 10000 \u0026#34;$songs\u0026#34; );; newsboat) ( kitty -e newsboat );; music_player) ( # kitty -e $HOME/scripts/musak.sh kitty -e ncmpcpp ) ;; radio_player) ( kitty -e pyradio --use-player vlc ) ;; killer) ( killall mpv );; esac play_music.sh #!/bin/bash answer=$1 artist=$2 if [ ${#answer} = 0 \u0026amp;\u0026amp; ${#artist} = 0 ] then read -p \u0026#34;Clear all songs (c) :: Play all songs (a) :: Number of songs (number) :: Query for songs (q) -\u0026gt; \u0026#34; answer fi clear allsongs=`mpc listall | wc -l` if [ -z $answer ] then answer=0 fi case $answer in \u0026#39;x\u0026#39;) read -p \u0026#34;Enter search term: \u0026#34; artist songs=`mpc listall | grep -i \u0026#34;$artist\u0026#34; | wc -l` mpc clear; mpc listall | grep -i \u0026#34;$artist\u0026#34; | shuf | mpc add; mpc play; mpc playlist printf \u0026#34;\\nShuffling $songs songs\\n\\n\u0026#34; ;; \u0026#39;0\u0026#39;) printf \u0026#34;\\nNothing to see here!\\n\\n\u0026#34; ;; \u0026#39;c\u0026#39;) mpc clear printf \u0026#34;\\nQueue is now clear\\n\\n\u0026#34; ;; \u0026#39;a\u0026#39;) mpc clear; mpc listall | shuf -n $allsongs | mpc add; mpc play; mpc playlist printf \u0026#34;\\nShuffling $allsongs songs\\n\\n\u0026#34; ;; \u0026#39;q\u0026#39;) if [ ${#artist} = 0 ] then read -p \u0026#34;Enter search term: \u0026#34; artist else list1=$(mpc listall | grep -i \u0026#34;$artist\u0026#34;) list2=`mpc search artist $artist` fi if [ ${#list1} -ge 1 ] then list=`echo -e \u0026#34;$list1\\n$list2\u0026#34;` else list=`echo -e \u0026#34;$list2\u0026#34;` fi songs=`echo -e \u0026#34;$list\u0026#34; | wc -l` if [ ${#list} -ge 1 ] then mpc clear; echo -e \u0026#34;$list\u0026#34; | shuf | mpc add; mpc play; mpc playlist printf \u0026#34;\\nShuffling $songs songs\\n\\n\u0026#34; fi ;; *) mpc clear; mpc listall | shuf -n $answer | mpc add; mpc play; mpc playlist printf \u0026#34;\\nShuffling $answer songs\\n\\n\u0026#34; ;; esac Radio Play a radio station In your i3-gaps ( or i3 ) configuration file, put the following line:\nbindsym $alt+Ctrl+r exec ~/scripts/radio.sh #!/usr/bin/env bash BROWSER=mpc MYFILE=\u0026#34;$HOME/.config/pyradio/stations.csv\u0026#34; # Read array of options to choose. readarray -t stations \u0026lt; \u0026#34;$MYFILE\u0026#34; mylist=$(printf \u0026#39;%s\\n\u0026#39; \u0026#34;${stations[@]}\u0026#34; | awk -F, \u0026#39;{out=\u0026#34;\u0026#34;; for(i=1;i \u0026lt; NF;i++){out=out\u0026#34; \u0026#34;$i}; print out \u0026#34;  \u0026#34; $NF}\u0026#39; | sort | uniq) # Piping the above lists into dmenu. # We use \u0026#34;printf \u0026#39;%s\\n\u0026#39;\u0026#34; to format the array one item to a line. # The urls are listed quickmarks first, then the SEPARATOR, and then bookmarks. choice=$(printf \u0026#39;%s\\n\u0026#39; \u0026#34;$mylist\u0026#34; | rofi -dmenu -i -l 25 -p \u0026#34;Open URL ($BROWSER)\u0026#34;) \u0026#34;$@\u0026#34; || exit if [ \u0026#34;$choice\u0026#34; ]; then url=$(echo \u0026#34;${choice}\u0026#34; | awk \u0026#39;{print $NF}\u0026#39;) || exit a=${url%$\u0026#39;\\r\u0026#39;} echo $a mpc clear mpc add $a mpc play #mpc clear \u0026amp;\u0026amp; mpc add $a \u0026amp;\u0026amp; mpc play # What to do if we just escape without choosing anything. else echo \u0026#34;Program terminated.\u0026#34; \u0026amp;\u0026amp; exit 0 fi Play LBC Radio ( UK Radio Station ) In your i3-gaps ( or i3 ) configuration file, put the following line:\nbindsym $alt+Ctrl+l exec ~/scripts/lbc.sh #!/bin/bash is_playing=$(mpc|awk \u0026#39;/LBC/ {print $2}\u0026#39;) if [[ ${#is_playing} -gt 0 ]] then mpc clear else mpc clear \u0026amp;\u0026amp; mpc add http://media-ice.musicradio.com/LBCLondon \u0026amp;\u0026amp; mpc play fi Miscellaneous Test the microphone In your i3-gaps ( or i3 ) configuration file, put the following line:\nbindsym $alt+Ctrl+c exec ~/scripts/git/test_mic.sh #!/bin/sh file=\u0026#34;test_mic.wav\u0026#34; duration=5 dunstify \u0026#34;Recording for $duration seconds into $file\u0026#34; arecord -d $duration $file dunstify \u0026#34;Now playing $file\u0026#34; aplay $file dunstify \u0026#34;$file has been deleted\u0026#34; rm $file Random Wallpaper changer In your i3-gaps ( or i3 ) configuration file, put the following line:\nbindsym $alt+Ctrl+w exec ~/scripts/random_wp.sh #!/bin/bash feh --bg-scale \u0026#34;$(find ~/Pictures/wallpapers/* -type f |sort -R |tail -1)\u0026#34; \u0026amp; exit 0 Toggle VPN (requires expressvpn) In your i3-gaps ( or i3 ) configuration file, put the following line:\nbindsym $alt+Ctrl+v exec ~/scripts/git/dmenu-vpn.sh #!/bin/bash # Witten by Keith Armstrong, Belkast Consulting # The expressvpn service is reqired, and is expected to be running d_vpns() { local vpns mapfile -t vpns \u0026lt; \u0026lt;(expressvpn list all | awk \u0026#39;{print $1\u0026#34; \u0026#34;$4\u0026#34; \u0026#34;$2}\u0026#39;) vpns=(\u0026#34;${vpns[@]:1}\u0026#34;) # removed the 1st element vpns=(\u0026#34;${vpns[@]:1}\u0026#34;) # removed the 1st element if (( ${#vpns[@]} \u0026gt; 1 )) ; then { printf \u0026#39;%s\\n\u0026#39; \u0026#34;status [ Get current status ]\u0026#34; printf \u0026#39;%s\\n\u0026#39; \u0026#34;disconnect [ Disconnect from VPN ]\u0026#34; printf \u0026#39;%s\\n\u0026#39; \u0026#34;${vpns[@]}\u0026#34; | sort -f } | dmenu -l 20 -c -b -fn \u0026#39;Droid Sans Mono-16\u0026#39; -sb \u0026#34;#9900cc\u0026#34; -h 30 -bw 3 -i -p \u0026#39;\u0026#39; ${dmenu_args[@]} fi } cmd=$(d_vpns | awk \u0026#39;{print $1}\u0026#39;) if [[ $cmd == \u0026#39;disconnect\u0026#39; ]] || [[ -z $cmd ]]; then expressvpn disconnect elif [[ $cmd == \u0026#39;status\u0026#39; ]]; then varStatus=$(expressvpn status | sed \u0026#39;s/\\x1B\\[[0-9;]\\{1,\\}[A-Za-z]//g\u0026#39; | head -n 1) dunstify -t 5000 \u0026#34;$varStatus\u0026#34; else expressvpn disconnect expressvpn connect $cmd fi ","date":"10 April 2016","externalUrl":null,"permalink":"/posts/linux/bash/","section":"Posts","summary":" If you want to reuse one of the scripts on this page, please ensure that you also have the following programs installed. i3 or i3-gaps dmenu rofi dunstify kitty zsh mpc putty qutebrowser xdotool warp feh expressvpn pcmanfm pyradio arecord aplay The necessary fonts dmenu / rofi Select an SSH session (requires putty) In your i3-gaps ( or i3 ) configuration file, put the following line:\n","title":"Random Bash scripts","type":"posts"},{"content":" Introduction # Secondary accounts are identified and linked to their owner’s Primary account. Secondary accounts are governed by an identity lifecycle – with options to remove, reassign, or transfer the access and privileges. Secondary accounts may be managed through authoritative source events on the Primary account – and through workflow / lifecycle processes. To get a flavour of what Secondary Account Management is all about, check out the video below. The solution we have developed is truly unique and is based on the standard NetIQ Identity Management product; the following capabilities have been implemented:\nAccount Management # Account Transfer # A Secondary Account can be transferred from one owner to another, either automatically or manually. The automatic Use Case is when the owner, or Primary Account, is Terminated. This termination can be configured to happen when, for example, the Primary Account goes to an EmployeeStatus of X or D or No Longer Employed. The lookup table can be configured to define what a termination actually is.The manual Use Case is when the Trigger attribute is updated with transfer-account|\u0026lt;account-type\u0026gt;|\u0026lt;account-name\u0026gt;|\u0026lt;new-owner\u0026gt;\nWhen a Secondary Account is transferred, there are a few things that can happen…and they can all be configured per ‘account-type’:\nData can be copied from the new Owner to the Secondary Account If desired, a new password can be assigned to the Secondary Account If desired, an expiration date can be assigned to the Secondary Account; this logic is driven off data present on the new owner All of these events can be configured in a lookup table row assigned to each ‘account-type’.\nResource Model # SAML can be configured to work with or without the NetIQ User Application Resource Model. If you don’t have the User Application, no problem. Just flip a GCV and the SAML Connector will take care of the rest.\nIn the situation where the User Application is not being used, SAML will replicate the content of the nrfAssignedResources attribute, thereby ensuring that there are no code changes to be made when changing to the non User Application model.\nAccount Creation # To create a new Secondary Account, you can assign a resource to a User from within the User Application. Because the outcome of the User Application resource assignment is an update to the Trigger attribute, you can also just update the Trigger attribute directly on the User Object.\nThe format of the value to insert in the Trigger attribute is assign-resource|\u0026lt;account-type\u0026gt;|\u0026lt;account-name\u0026gt;. For the account-name portion, you can supply your own value or use [] to generate a name based on the setting in the lookup table for the account-type.\nEither of these two methods will result in the creation of a Secondary Account.\nAccount Deletion # To delete a Secondary Account, either revoke the Resource from within the User Application ( with the correct settings present in the lookup table ), or just delete the Secondary Account using an LDAP browser.\nHowever, please note that the revocation results in the deletion, the disabling, or the transferring of the Secondary Account based on the lookup table settings.\nPrimary Account Termination # When the Primary User Account is terminated, SAML really comes in to its own. The trigger which kicks off a termination is based on an attribute change; for example, employeeStatus changing to INACTIVE or termination date gaining a value.\nBased on the settings in the lookup table, the Secondary Accounts owned by the Primary Account will be Disabled, Transferred, or Deleted.\nWhen a Secondary Account is Transferred, you can configure SAML to ensure that the data from the new Owner is copied over to the Secondary Account.\nData Flow # Attribute Synchronization # A value in the main lookup table is assigned to each ‘account-type’, with the value pointing to a child lookup table. This gives SAML the flexibility to share data synchronisation models between multiple types of Secondary Account. Attribute synchronisation can be configured for:\nPrimary account -\u003e Secondary account Secondary account -\u003e Primary account Secondary account \u003c-\u003e Primary account Attribute reset Required Attributes # Required attributes can be specified for the creation of a Secondary account. Similar to the built-in function veto-if-required-attribute-not-available, SAML uses a lookup table to determine whether an attribute is required before a Secondary Account is created. Attributes can be added and removed on-the-fly. The required attributes setting is per account-type.\nDefault Attributes # Default attributes can be assigned on the creation of a Secondary account (per account-type). This is simply a case of updating the relevant row in the lookup table. For example, you can assign one or more Entitlement values when a new Secondary Account is created.\nOther Features # Lock down of Secondary account request based on attribute data (Department, Job Code, etc.) The Secondary account can be placed in a predefined Organizational Unit (per account-type) The Secondary account can be assigned an expiration date (per account-type) A Primary Account can have as many Secondary Accounts as the lookup table is configured to allow A maximum number of Secondary Accounts can be defined (per account-type) Secondary Accounts can be blacklisted: if account type A then not account-type B Representation of Primary Account Data # The image below shows a representation of the data held on the Primary account. There are a couple of attributes on the Primary account that tie the data synchronisation together: There is a DirXML-Association value per Secondary account (not visible in the image) and there exists the Distinguished Name (DN) of the Secondary account in the idamSecondaryAccountDNs multi-valued attribute. You might also notice in the image that the nrfAssignedResources attribute has a couple of values – the number of values just happen to correspond with the number of Secondary accounts assigned to the Primary account.\nRepresentation of Secondary Account Data # The image below shows a representation of the data held on the Secondary account. Once again, there is a DirXML-Association value linking the Secondary account with the Primary account (not visible in the image), there exists the Distinguished Name (DN) of the Primary account in the idamPrimaryAccountDN single-valued attribute, and the relevant value contained in the nrfAssignedResources attribute from the Primary account is also held on the Secondary account in the idamAssignedResource attribute.\nWhat makes this offering unique and a proper Solution is the fact that, as well as the SAM Connector code, we have also developed several sophisticated Roles Based Provisioning Module (RBPM) Workflow forms to facilitate the requesting and maintaining of Secondary accounts.\nRequest Form 1 # Request Form 2 # Email to Line Manager # Approval by Line Manager # Approval by Service Desk # Email when the request is approved # Email when Secondary account is created # ","date":"15 April 2013","externalUrl":null,"permalink":"/secondary-accounts/","section":"Posts","summary":"Introduction # Secondary accounts are identified and linked to their owner’s Primary account. Secondary accounts are governed by an identity lifecycle – with options to remove, reassign, or transfer the access and privileges. Secondary accounts may be managed through authoritative source events on the Primary account – and through workflow / lifecycle processes. To get a flavour of what Secondary Account Management is all about, check out the video below. The solution we have developed is truly unique and is based on the standard NetIQ Identity Management product; the following capabilities have been implemented:\n","title":"Secondary Accounts","type":"posts"},{"content":"A quick example of how to start using author taxonomies in your articles.\n","externalUrl":null,"permalink":"/authors/","section":"Authors Taxonomy Listing Example","summary":"A quick example of how to start using author taxonomies in your articles.\n","title":"Authors Taxonomy Listing Example","type":"authors"}]