Introducing Maruti-Zsh: a Custom, High-Performance Zsh Engine

Introduction

We've all been there. You install Zsh, and the very next recommendation you see online is to install Oh-My-Zsh. For a long time, I did exactly that. But over time, the "magic" of heavy frameworks started to wear thin.
Maruti-zsh
Recently, my old web server hosted on a 2011 Macbook Pro running Ubuntu 22.04 started failing and was on life support. I was not ready to spend a lot of money on a replacement computer and was scouring Facebook Marketplace, Craigslist, Nextdoor to get a cheap computer. Incidentally I got a free PC from 2010 with a potentially bad hard drive.

I was ready to troubleshoot and make it work and I set out to repurpose that PC as a lean web server. After extensive research on the right OS, I decided that I will use Debian 13. On hardware of that vintage, every megabyte of RAM and every CPU cycle matters. When I loaded a standard framework onto it, I noticed a distinct, frustrating lag before the prompt appeared.

The terminal shouldn't lag. It should feel like the wind—instant, unburdened, and powerful.

So I stripped everything out and built a native, modular Zsh configuration engine from scratch. I named it Maruti-Zsh, after the ancient Indian deity of the wind, celebrated for immense strength hidden within a modest form.

Here is how I built it, the breaking point that almost locked me out of my own server, and why you might want to drop the frameworks too.


The Philosophy: Zero Frameworks, Zero Bloat

The core issue with modern command-line setups isn't Zsh itself; it's the "black box" plugins that load hundreds of lines of third-party script architecture you will never use.

Maruti-Zsh returns to native Zsh internals. By targeting the Zsh Line Editor (ZLE) directly, you can achieve sophisticated features—like syntax highlighting, auto-suggestions, and deep history tracking—with instant startup times.

To keep things organized without a framework, I built a modular architecture. Instead of one massive, unmanageable .zshrc file, the configuration lives in a dedicated directory:

~/.maruti-zsh/
  └── .zsh/
       ├── key-bindings.zsh    # Custom ZLE widgets and shortcuts
       ├── zsh-aliases.zsh     # Navigation and tool aliases
       ├── zsh-completion.zsh  # Completion system and zstyle config
       ├── zsh-history.zsh     # History size and optimizations
       └── plugins/            # Pure git-cloned plugins

The loader in .zshrc sources everything in that directory with a single glob — no hardcoded entries, no framework overhead:

for f in ~/.maruti-zsh/.zsh/*.zsh(N); do source "$f"; done

The (N) qualifier is a Zsh glob flag that suppresses errors if the directory is empty, making this safe to run even on a fresh install.


The Engine: Modular Sourcing (And How I Broke It)

My first attempt at the loader looked like this:

# Do not copy this version!
for f in ~/.zsh/configs/**/*.zsh(N); do source "$f"; done

It worked beautifully on my laptop. But when I deployed it to the server and ran source ~/.zshrc, the terminal instantly kicked me out, locked the session, and refused to let me back in, screaming about running out of BUFFER space.

The Lesson

I had accidentally created a circular recursive loop. The ** globbing pattern was too broad and caught a file that re-triggered the initialization sequence. Because each source execution consumes stack memory, the shell suffered an instant buffer overflow and the OS killed the process.

If you ever find yourself locked out by a broken shell config, you can bypass your initialization files over SSH like this:

ssh -t user@server_ip "zsh --no-rcs"

Once back in, the fix was simple: scope the glob precisely to the .zsh/ directory using *.zsh instead of **/*.zsh, so it only sources files immediately in that folder with no recursive descent:

for f in ~/.maruti-zsh/.zsh/*.zsh(N); do source "$f"; done

With the engine safely loading files, I could start building out the actual configuration modules.

History That Actually Works

Before any widgets, the first thing to get right is history. The defaults Zsh ships with are surprisingly barebones. Here is the full configuration in zsh-history.zsh:

HISTFILE=~/.zsh_history
HISTSIZE=10000
SAVEHIST=10000

setopt HIST_IGNORE_ALL_DUPS  # Don't record duplicate commands
setopt HIST_REDUCE_BLANKS    # Strip superfluous whitespace
setopt HIST_VERIFY           # Show the expanded command before running it
setopt SHARE_HISTORY         # Share history across all open terminals instantly
setopt HIST_IGNORE_SPACE     # Commands prefixed with a space are never saved

That last one — HIST_IGNORE_SPACE — is genuinely useful for secrets and one-off commands you don't want persisted. Prefix any command with a space and it disappears from history entirely.


Tab Completion: Native Power, No Plugin Required

One of Zsh's most underappreciated advantages over Bash is its built-in completion system. It doesn't need a plugin — it just needs to be initialized and configured. This lives in its own module, zsh-completion.zsh, keeping it separate and easy to modify

# Initialize the completion system
autoload -Uz compinit && compinit

# Arrow-key navigable completion menu
zstyle ':completion:*' menu select

# Colorized completions using existing LS_COLORS
zstyle ':completion:*' list-colors "${(s.:.)LS_COLORS}"

# Group completions by category
zstyle ':completion:*' group-name ''

# Label each category group
zstyle ':completion:*:descriptions' format '%F{yellow}-- %d --%f'

# Auto-find newly installed executables without restarting the shell
zstyle ':completion:*' rehash true

What Each Line Does

autoload -Uz compinit && compinit activates the entire completion engine. Without this, Zsh's tab completion falls back to basic filename expansion — functional, but nowhere near its potential.

menu select turns the completion list into a navigable menu. Instead of cycling blindly through candidates with repeated Tab presses, you get a visual list you can move through with arrow keys and select with Enter. This alone is worth the setup.

list-colors pipes your existing $LS_COLORS environment variable into the completion menu, so directories, executables, symlinks, and files all render in the same colors you already see in ls output. The cryptic ${(s.:.)LS_COLORS} is Zsh parameter expansion syntax — (s.:.) splits the string on : into an array that zstyle can consume.

group-name '' tells Zsh to separate completions into named categories — files in one group, shell builtins in another, external commands in another — rather than dumping everything into a single flat list.

format '%F{yellow}-- %d --%f' adds a yellow category label above each group. %d is replaced with the group description, and %F{yellow} / %f are Zsh prompt color codes. Small touch, significant readability improvement.

rehash true tells the completion system to scan $PATH for new executables automatically. Without this, if you install a new tool mid-session, Zsh won't offer it as a completion candidate until you start a new shell or run rehash manually.

A Note on Case Sensitivity

Zsh's completion system supports case-insensitive matching via zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}'. Maruti-Zsh deliberately leaves this out. Linux is a case-sensitive operating system and the completion system should reflect that — if you type doc, you should get doc, not Documents. The discipline of knowing your filenames matters, and the completion menu shouldn't paper over it.


Power Moves: Custom ZLE Widgets

Without a framework, how do you get premium features? You write small, targeted functions using the Zsh Line Editor (ZLE). ZLE is essentially a tiny text editor living inside your command line — it lets you define functions that manipulate the command buffer directly, then bind them to any key combination you want.

Here are the custom shortcuts in Maruti-Zsh that change how you interact with the shell.

1. The Sudo Toggle (Alt+S)

We've all typed a long command, hit Enter, and received a Permission denied error. Instead of retyping or reaching for sudo !!, this widget toggles sudo on and off at the front of whatever is currently in your buffer:

sudo-command-line() {
    [[ -z $BUFFER ]] && zle up-history
    if [[ $BUFFER == sudo\ * ]]; then
        LBUFFER="${LBUFFER#sudo }"
    else
        LBUFFER="sudo $LBUFFER"
    fi
}
zle -N sudo-command-line
bindkey '\es' sudo-command-line

LBUFFER is everything to the left of the cursor. By prepending or stripping sudo from it, the widget works correctly regardless of where your cursor is in the line.

2. Smart Clear and List (Ctrl+K)

The standard Ctrl+L clears the screen. The Maruti-Zsh version wipes the screen and immediately runs ls so you always have immediate context about your working directory. It's OS-aware — -G for macOS, --color=auto for Linux:

clear-ls-widget() {
    clear
    if [[ "$OSTYPE" == "darwin"* ]]; then
        ls -pG
    else
        ls -p --color=auto
    fi
    zle redisplay
}
zle -N clear-ls-widget
bindkey '^K' clear-ls-widget

zle redisplay tells the line editor to re-render the prompt at the bottom after the ls output, so your cursor is always in the right place.

3. Inline Search Pipes (Alt+G)

When parsing logs or command output, you end up typing | grep constantly. This widget appends it to your buffer instantly and positions the cursor ready to type the search term:

wrap-grep() {
    BUFFER="$BUFFER | grep "
    CURSOR=$#BUFFER
}
zle -N wrap-grep
bindkey '\eg' wrap-grep

4. Edit Command in $EDITOR (Ctrl+O)

When a one-liner gets too complex to manage on a single line, Ctrl+O drops the entire buffer into your $EDITOR (vim, nano, or whatever you have set). Save and quit, and the command executes:

autoload -Uz edit-command-line
zle -N edit-command-line
bindkey '^O' edit-command-line

5. History Prefix Search (Arrow Keys)

This one is simple but transforms how you use history. Bound to the up and down arrow keys, it searches history by whatever prefix you've already typed — so if you type git and press up, you only cycle through previous git commands:

bindkey '^[[A' up-line-or-search
bindkey '^[[B' down-line-or-search


Full Keybinding Reference

Binding Action
Alt+← / Alt+→ Move backward / forward one word
Ctrl+A Jump to beginning of line
Ctrl+E Jump to end of line
/ History search by prefix
Alt+. Insert last argument from previous command
Ctrl+Backspace Delete previous word
Ctrl+Delete Delete next word
Ctrl+U Clear entire command buffer
Ctrl+L Kill from cursor to end of line
Alt+S Toggle sudo on current line
Alt+G Append | grep to current command
Ctrl+K Clear screen + show directory listing
Ctrl+O Open current command in $EDITOR
Ctrl+X N Jump to /etc/nginx/sites-available/
Ctrl+X W Jump to /var/www/html/

Making It a Reusable Product

Once the environment was rock-solid, I packaged it into a clean open-source project with a dedicated installer and uninstaller.

The install.sh script handles orchestration safely. It expects you to have zsh installed, verifies its presence, and fails fast if it's missing:

if ! command -v zsh >/dev/null 2>&1; then
    echo "❌ Error: Zsh is not installed on this system."
    echo "Please install Zsh using your system's package manager first."
    exit 1
fi

From there it sets up the directory structure, clones Powerlevel10k and the two plugins (zsh-autosuggestions and zsh-syntax-highlighting) using --depth=1 shallow clones to stay lean, handles font installation with OS-aware paths (~/Library/Fonts on macOS, ~/.fonts on Linux), and drops you into a fresh session.

The uninstall.sh is a complete cleanup utility — it removes the configuration directory, Powerlevel10k, and the .zshrc, then restores either the system default from /etc/skel/.zshrc or creates a clean empty one if no system default exists.


Extending It

The modular loader means adding your own configuration is trivial. Drop any .zsh file into ~/.maruti-zsh/.zsh/ and it will be sourced automatically on the next shell start — no edits to .zshrc required:

# Example: add your own work-specific aliases
nano ~/.maruti-zsh/.zsh/work-aliases.zsh


Conclusion

Dropping Oh-My-Zsh forced me to actually understand the tool I spend hours inside every single day. My terminal prompt now renders instantly, my memory footprint is negligible, and every line of my shell configuration is something I wrote and fully understand.

The circular sourcing bug that locked me out of my own server was the best thing that could have happened — it forced me to understand how Zsh glob patterns and stack memory interact, and the resulting design is tighter for it.

If you want to try it, modify it, or use it to breathe new life into an older machine, the repository is fully documented and open for customization:

github.com/mukuld/maruti-zsh

Nginx vs. WordPress: The 1MB Limit You Didn’t Know You Had

If you run a WordPress site on a lean Linux server, you’ve likely faced this brick wall: you try to upload a seemingly small image, and WordPress throws a frustratingly vague error.

"The server cannot process the image. This can happen if the server is busy or does not have enough resources to complete the task. Uploading a smaller image may help. Suggested maximum size is 2560 pixels."

Your first instinct might be to look at your server stats. You see a CPU spike, RAM usage climbs, and you conclude, "I need a bigger server." But here’s the catch: you could migrate to a machine with 64GB of RAM and a 16-core CPU, and you would still see this error. The "resource" issue isn't your hardware; it's a silent gatekeeper in your web server configuration.

The Illusion of "Not Enough Resources"

When an upload fails, WordPress assumes the environment is overwhelmed. However, the real culprit is often a hard-coded security limit that exists on even the most powerful enterprise servers, long before your image ever reaches PHP or the WordPress media library.

To find the truth, stop looking at your WordPress dashboard and start looking at your Nginx error logs. By running a simple command while attempting an upload, you'll find the smoking gun:

tail -f /var/log/nginx/error.log

You will likely see a clear message that reveals the true cause:

2026/03/25 09:41:57 [error] 156998#156998: *75 client intended to send too large body: 1477858 bytes, client: xxx.xxx.xxx.xxx, server: [www.dharwadkar.com](https://www.dharwadkar.com), request: "POST /wp-admin/async-upload.php HTTP/2.0"

The translation is simple: Nginx has a default upload limit of 1MB (client_max_body_size). A 1.4MB image is blocked before it even gets a chance to be processed.

The Solution: Fixing the Configuration, Not the Hardware

To solve this for good, you need to adjust the limits in the three layers of your stack.

Layer Action Configuration
1. Web Server (Nginx) Tell Nginx to allow larger file uploads. Edit your site configuration (e.g., /etc/nginx/sites-available/default) and add this directive inside the server or http block. client_max_body_size 64M;
2. PHP (The Processor) Ensure PHP is ready to handle the larger file. Edit your php.ini (e.g., /etc/php/YOUR_VERSION/fpm/php.ini). Note: Replace YOUR_VERSION with your actual PHP version (e.g., 8.1, 8.2). upload_max_filesize = 64M
post_max_size = 64M
3. Image Library (The Worker) For better performance, ensure the ImageMagick library is handling image processing instead of the default GD library. This helps prevent actual resource exhaustion when processing large images. sudo apt install php-imagick

After making changes, don't forget to test your web server configuration and reload the necessary services:

# For Nginx
sudo nginx -t && sudo systemctl reload nginx

# For PHP-FPM (remember to use your version)
sudo systemctl restart php[YOUR_VERSION]-fpm

The Apache Alternative

If you are using Apache, the equivalent directive to Nginx's client_max_body_size is LimitRequestBody. While often set to unlimited by default, some hardened configurations restrict it. You can add this to your .htaccess file (the value is in bytes):

# 64 MB in bytes
LimitRequestBody 67108864

Verification: Is It Working?

Once you’ve lifted the gate, you should verify that WordPress is using the right tools to process your images.

  1. Go to your WordPress Dashboard.
  2. Navigate to Tools > Site Health.
  3. Click the Info tab and expand the Media Handling section.

You want to see ImageMagick listed as the "Active editor." If it says "GD" or is missing, you may need to install the module with sudo apt install php-imagick and restart your PHP service.

The Takeaway: Logs Over Guesses

This is a classic "edge case" in systems administration where the error message points you in the wrong direction. The issue isn't a lack of resources but a simple configuration designed to protect the server.

Remember these key lessons:

  • WordPress errors are generic: They describe the symptom, not the cause.
  • Layers matter: An upload goes through Web Server → PHP → WordPress. A failure at any layer breaks the chain.
  • Check the logs first: A few seconds in the error log can save you hours of guessing and prevent you from wasting money on unnecessary hardware upgrades.

Automating Git Workflow: Simple to Pro with Gotchas

The Spark

My daughter is currently navigating the intense, fast-paced world of software engineering. Recently, she hit that universal developer wall—the tedium of the "Git Triple-Step." After a long session of coding, she exclaimed, "There has to be a faster way to do this. I'm tired of typing the same three commands every time I want to save my work!"

As a tinkerer who can’t resist a good challenge, I told her, "Don't worry, I’ll write you a script. One command, and you're done."

It sounded simple and it was simple. But as I quickly learned, Bash has a way of humbling even the most experienced scripters when you least expect it.


The "Simple" Script That Failed

I cobbled together a string of commands which I thought was a foolproof one-liner. I wanted the script to take her message, add the files, commit them, and push.

#!/bin/bash
msg=$1
git add .
git commit -m \"$msg\"
git push

I tested it with a single word: ./push Test. It worked perfectly. It felt real good.

Then she tried a real-world commit message: ./push "Added the new NN logic"

The Result? Disaster. The terminal started screaming: error: pathspec 'new' did not match any file(s).


The Anatomy of the "Pathspec" Bug

What went wrong? As it would happen, I had fallen into the Literal Quote Trap.

By using backslashes (\"$msg\"), I thought I was being proactive by "forcing" the quotes to stay around the message. Instead, I was telling Bash to treat the quotation marks as literal text characters rather than functional shell wrappers.

When the script executed, Git didn't see a single string. It saw:

  • -m: The flag
  • "Added: The message—including a literal opening quote
  • the, new, NN...: Git thought these were filenames I wanted to commit.

Since those files didn't exist, Git threw a "pathspec" error and died.


The Fix: Double-Quote to the Rescue

In Bash, you don't need to "force" quotes with backslashes. You just need to wrap your variable expansion in standard double quotes. This acts as the "glue" that keeps your multi-word message together, no matter how many spaces it has.


The "Pro" Solution: push.sh

After fixing the quoting bug, I decided to take it a step further. If I was going to give my daughter a tool, it should be robust, safe, and informative.

Here is the final "Pro" version of the script. It includes:

  • Branch Protection: Prevents accidental pushes to main or master.
  • Color-Coded Feedback: Green for success, Red for "Stop and Look."
  • Safety Checks: If the commit fails, it won't try to push.

The Script

#!/bin/bash

# -----------------------------------------------------------------------------
# Script Name: push
# Description: Automates the 'git add .', 'git commit', and 'git push' workflow
#              with built-in branch protection and error handling.
# Developer:   Mukul Dharwadkar
# License:     MIT (Open Source - Please retain developer credit)
# -----------------------------------------------------------------------------

# --- CONFIGURATION ---
# Colors for high-visibility feedback
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# --- HELP MENU ---
function show_help {
    echo -e "${YELLOW}push - Automated Git Workflow${NC}"
    echo -e "Developed by: Mukul Dharwadkar"
    echo -e ""
    echo -e "${YELLOW}Usage:${NC} ./push \"Your commit message\""
    echo -e "Example: ./push \"Refactored navigation bar CSS\""
    exit 1
}

# 1. Validation: Ensure a message was provided
if [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]] || [[ -z "$1" ]]; then
    show_help
fi

# 2. Capture the Message
# We use "$*" to grab every word passed to the script as one string.
msg="$*"

# 3. Branch Safety Check
current_branch=$(git rev-parse --abbrev-ref HEAD)

if [ "$current_branch" == "main" ] || [ "$current_branch" == "master" ]; then
    echo -e "${RED}⚠️  WARNING: You are on the $current_branch branch.${NC}"
    read -p "Are you sure you want to push directly to production? (y/n) " -n 1 -r
    echo
    if [[ ! $REPLY =~ ^[Yy]$ ]]; then
        echo -e "${YELLOW}Push aborted. Safe choice!${NC}"
        exit 1
    fi
fi

# 4. The Execution Chain
echo -e "${GREEN}Staging all changes...${NC}"
git add .

echo -e "${GREEN}Committing with message:${NC} \"$msg\""
# Wrapping $msg in double quotes (no backslashes!) is the secret sauce.
if git commit -m "$msg"; then
    echo -e "${GREEN}Pushing to origin/$current_branch...${NC}"
    if git push; then
        echo -e "${GREEN}✅ Success! Local and Remote are synced.${NC}"
    else
        echo -e "${RED}❌ Error: Push failed. Check your network or remote permissions.${NC}"
    fi
else
    echo -e "${RED}❌ Error: Commit failed. (Likely no changes to save).${NC}"
fi

How to Use It

  1. Save it: Copy the code into a file named push.sh.
  2. Permit it: Run chmod +x push.sh to make it executable.
  3. Deploy it: Move it to your /usr/local/bin folder to use it from any project.

Note: To make it even easier to run, you can choose to omit the .sh extension and just keep it as push or whatever name you choose.


Final Thoughts

Automation isn't just about saving time; it's about building guardrails. Now, the git workflow moves faster, but the script is there to ensure the developer indeed wants to push to the main (or master) branch.

The next time you’re building a "simple" script and it breaks, remember: Check your quotes. Bash is a powerful ally, but it demands literal precision.

Intune Win32 App Detection Woes? The Registry Path in Manual Detection Rules Might Be Your Culprit!

Are you an Intune administrator tearing your hair out over Win32 app deployments that report "The application was not detected after installation completed successfully (0x87D1041C)"? Or perhaps you're getting the cryptic "Invalid detection rule or unable to parse detection rule (0x87D30006)" error?

If your app installation seems fine, but Intune just won't acknowledge it, and you're relying on registry key detection, especially for user-specific settings (HKCU), you've landed on the right page. This post will detail a common pitfall and how I recently overcame it, hopefully saving you hours of troubleshooting!


The Scenario: User-Context App, HKCU Registry, and Frustrating Errors

I was deploying a Win32 application through Intune, specifically an Outlook add-in, that needed to modify settings in the current user's registry hive (HKEY_CURRENT_USER). My installation script successfully set the LoadBehavior for the add-in to 2 (disabled) under HKCU:\SOFTWARE\Microsoft\Office\Outlook\Addins\.... Outlook on the target machines was 64-bit.

Everything seemed to work on the client side: the add-in was disabled as expected, and I could confirm the registry entries were present and correct in regedit.

However, Intune stubbornly reported:

  • 0x87D1041C: "The application was not detected after installation completed successfully."

This was incredibly frustrating. The app was installed and configured. Why couldn't Intune see it?


Initial Suspicions (and why they weren't the main issue here)

Like many, my first thoughts turned to:

  1. 32-bit vs. 64-bit Registry Redirection (WOW6432Node): This is a classic trap. If a 32-bit app writes to HKLM\SOFTWARE on a 64-bit OS, it gets redirected to HKLM\SOFTWARE\WOW6432Node. But in my case, Outlook was 64-bit, and the registry path was HKCU, which generally doesn't involve WOW6432Node redirection in the same way. So, this wasn't the culprit.
  2. Timing Issues: Could Intune's detection scan be running too quickly, before the registry entries were fully committed? While possible, it's less common for direct registry writes in HKCU after an installer completes.
  3. Typos in Path/Value: Double and triple-checked – the paths and value names were correct.

The Real Culprit: Intune UI's Specific Registry Path Syntax

After much digging and trial-and-error, the issue boiled down to a subtle but critical detail: the precise syntax expected by Intune's manual registry detection rules, especially for HKEY_CURRENT_USER.

My instinct, coming from a PowerShell background, was to use HKCU:\SOFTWARE\Microsoft\.... When that didn't work, I tried SOFTWARE\Microsoft\... (omitting the hive altogether), which led to a new error:

  • 0x87D30006: "Invalid detection rule or unable to parse detection rule."

This error was the key! It told me that Intune wasn't just failing to find the key; it couldn't even understand the format of the path I was providing.

The Solution:

There is not definitive guidance from Microsoft on the correct and expected values in application detection rules when it comes to Registry rules. The documentation, though subtle, points to the answer: Intune's manual registry detection rule UI expects the full, standard Windows registry hive name (or its traditional short form) without a colon after the hive, followed by a backslash.

For HKEY_LOCAL_MACHINE, this is commonly seen as HKEY_LOCAL_MACHINE\Software\... or HKLM\Software\....

Following this logic, the correct syntax for my HKEY_CURRENT_USER detection rule was:

HKEY_CURRENT_USER\SOFTWARE\Microsoft\Office\Outlook\Addins\ShoreTel.CASConnHostAddIn

And similarly for the second add-in:

HKEY_CURRENT_USER\SOFTWARE\Microsoft\Office\Outlook\Addins\ShoreTel.UCBAddIn

Once I updated the "Key path" in my Intune Win32 app detection rules to this precise format, the app instantly detected correctly, and my deployment reported "Installed"!


Key Takeaways for Intune Registry Detection:

  • For HKEY_CURRENT_USER: Always use HKEY_CURRENT_USER\ as the prefix for your Key Path in Intune's manual detection rules.
  • For HKEY_LOCAL_MACHINE: Use HKEY_LOCAL_MACHINE\ or HKLM\ as the prefix.
  • Avoid PowerShell PSDrive Syntax: Do NOT use HKCU:\ or HKLM:\ in the Intune UI's "Key path" field for manual detection rules.
  • Leverage IntuneManagementExtension.log: For any detection issues, the log file at C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\IntuneManagementExtension.log on the client device is your best friend. Search for your app name or "Detection" to pinpoint the exact failure. It will tell you if the rule failed to parse or if the value didn't match.
  • 32-bit vs. 64-bit on HKLM: Remember the "Associated with a 32-bit app on 64-bit clients" checkbox for HKLM paths if your app is 32-bit and running on a 64-bit OS. (This wasn't an issue for my 64-bit Outlook and HKCU paths, but it's a common trap!)

I hope this detailed explanation saves you the headaches I experienced. Share your own Intune troubleshooting tips in the comments below!

Seamless VPN Automation: Empowering Entra ID-Only and Non-Domain Joined PCs in a Hybrid World

Picture this: A remote employee logs into their Entra ID-only laptop, expecting instant access to critical on-premises systems. Instead, they’re stuck troubleshooting a manual VPN connection, productivity stalls, and IT gets another support ticket. This is the reality for organizations using Microsoft’s AlwaysON VPN with non-domain joined or Entra ID-only devices—Microsoft doesn’t natively support automated Device Tunnel connections for these setups. I’ve built a lightweight, Windows-native solution to fix this gap, ensuring secure, seamless, and automated VPN connectivity for hybrid and cloud-first environments.


Why This Matters to businesses

The shift to cloud-first models like Microsoft Intune and Entra ID (formerly Azure AD) is transforming IT operations, enabling mobility and zero-trust architectures. However, hybrid environments remain a reality—many organizations still rely on on-premises servers for critical operations, and non-domain joined PCs are increasingly common in remote work setups. Without automation, these devices face:

  • Manual VPN Connections: Employees struggle to connect, disrupting workflows.
  • IT Overload: Support tickets pile up, diverting resources from strategic priorities.
  • Security Risks: Inconsistent connectivity increases vulnerabilities.
  • Productivity Loss: Employees waste time waiting for access to internal resources.

My solution bridges this gap, automating AlwaysON VPN Device Tunnel connections for Entra ID-only and non-domain joined Windows PCs. For decision-makers, this means reduced IT costs, improved employee satisfaction, and enhanced security. For IT teams, it’s a hands-off, scalable framework that leverages native Windows tools—no third-party software required.


Real-World Impact: A Game-Changer for Remote Work

Consider a mid-sized company with 200 remote employees using non-domain joined laptops. Without automation, IT spends hours weekly troubleshooting VPN issues, and employees lose productivity waiting for connections. After deploying our solution, VPNs auto-connect at boot, trusted networks are detected seamlessly, and logs provide IT with clear insights. The result? A secure, efficient, and frustration-free remote work experience—saving time, reducing costs, and ensuring consistent access to critical resources.


What My Solution Delivers

My PowerShell-based framework uses built-in Windows features for simplicity, reliability, and scalability. Here’s what it offers:

Feature Benefit
Effortless Connectivity Auto-connects VPN at startup, user logon, and network changes—no user action needed.
Trusted Network Detection Disconnects VPN on trusted networks (e.g., office LAN) to optimize performance.
Smart Retry Logic Retries failed connections intelligently with up to three attempts, ensuring reliability.
Detailed Logging Logs all actions in Windows Event Viewer for troubleshooting and compliance.
Self-Healing Monitors connectivity every minute, maintaining persistent access without intervention.
Hybrid and Entra ID Ready Supports non-domain joined and Entra ID-only PCs in cloud-on-premises setups.

For Decision-Makers: The Business Value

  • Cost Savings: Reduces IT workload by eliminating manual VPN management, freeing teams for strategic initiatives.
  • Enhanced Productivity: Employees access resources instantly, boosting efficiency and satisfaction.
  • Improved Security: Ensures consistent connectivity, reducing risks from user errors or missed connections.
  • Cloud-First Compatibility: Aligns with Entra ID-only deployments, supporting your cloud migration strategy.

For IT Teams: The Technical Edge

  • Native Tools: Uses PowerShell, VBScript, and Task Scheduler—no third-party dependencies.
  • Scalable Design: Works for 10 devices or 10,000, with robust error handling.
  • Security First: Runs as SYSTEM, hides scripts in a secure folder, and supports future script signing.

How It Works: A Peek Under the Hood

The solution comprises four lightweight scripts, working together to automate VPN connectivity:

  1. Installer (startAndCheckVPN.bat): Deploys scripts to a hidden C:\Scripts directory and triggers task creation.
  2. Task Scheduler (startAndCheckVPNTasks.ps1): Sets up a Scheduled Task to run at system startup, user logon, and every minute.
  3. Core Logic (startAndCheckVPN.ps1): Checks network status, manages VPN connections, and logs actions.
  4. Silent Runner (startAndCheckVPN.vbs): Executes PowerShell invisibly, ensuring no user disruption.

Step-by-Step Workflow

  • Device Boots or User Logs On: Task Scheduler triggers the VBScript.
  • Silent Execution: VBScript runs PowerShell without pop-ups.
  • Network Check: PowerShell verifies if the device is on a trusted network.
  • VPN Action: If not on a trusted network, it establishes the VPN with up to three retries; if on a trusted network, it disconnects the VPN if active.
  • Continuous Monitoring: Re-checks connectivity every minute, looping back to network and VPN status checks.
  • Logging: Records all actions (success, failure, network status) in Event Viewer under VPNConnectionScript.

For a visual representation, check out our workflow diagram
Diagram of automated AlwaysON VPN workflow for Entra ID-only and non-domain joined PCs

Get Started: Deployment Made Simple

Ready to transform your remote access strategy? Here’s how to deploy our solution:

  1. Prerequisites:

    • Windows 10/11 with AlwaysON VPN configured.
    • Administrative privileges.
    • Update placeholders in startAndCheckVPN.ps1 (e.g., $TrustedNetworkRange, $VpnName).
  2. Installation:

    git clone https://github.com/mukuld/automatedAlwaysONVPN.git
    cd automatedAlwaysONVPN
    .\startAndCheckVPN.bat
  3. Configuration:

    • Edit script variables to match your environment (e.g., VPN name, network ranges).
    • Verify the Scheduled Task is created (TaskName: “Automatically connect VPN on Startup and if not connected on user logon”).
  4. Test and Monitor:

    • Reboot the device to confirm auto-connectivity.
    • Check logs in Event Viewer (Application > VPNConnectionScript) for insights.

For detailed instructions, visit our GitHub repository.

Security and Compliance: Built for Enterprise Needs

  • SYSTEM Privileges: Runs without exposing user credentials.
  • Hardened Scripts: Stored in a hidden folder (C:\Scripts); can be code-signed for added security.
  • Audit-Ready: Comprehensive event logging for compliance tracking, with references to Microsoft’s PowerShell Event Logging.

Looking Ahead: Future Enhancements

We’re committed to evolving this solution to meet future needs:

  • Windows Service Integration: For even tighter OS integration.
  • Cross-Platform Support: Expand to macOS and Linux using native schedulers (e.g., launchd, cron).
  • Azure Monitor Telemetry: Add monitoring for enterprise-scale insights.
  • Modern Authentication: Integrate Azure MFA for enhanced security.
  • User Tray App: Build a simple interface for VPN status visibility.

Why Choose my solution?

  • Fills a Critical Gap: Automates AlwaysON VPN for Entra ID-only and non-domain joined PCs, addressing Microsoft’s limitation.
  • Drives Efficiency: Saves IT time, reduces costs, and boosts productivity.
  • Scalable and Secure: Designed for hybrid environments, from small teams to global enterprises.
  • Community-Driven: Open to contributions to enhance features and compatibility.

Let’s Collaborate: Join the Future of Remote Access

I am passionate about making remote work secure, seamless, and invisible to users. Ready to experience automated VPN connectivity like never before? Here’s how to get involved:

  • Try It Now: Clone the repository and test it in your environment.
  • Share Feedback: Have ideas to improve this solution? Email me at mukul@dharwadkar.com.
  • Contribute: Join me on GitHub to file issues or contribute code: GitHub Repository.

Let’s build a future where hybrid and cloud-first work is effortless. Download our solution today and empower your workforce with reliable, secure access.

Appendix

Script List:

  • startAndCheckVPN.bat
  • startAndCheckVPN.ps1
  • startAndCheckVPN.vbs
  • startAndCheckVPNTasks.ps1

References:


Copyright © 2025 Mukul Dharwadkar. All rights reserved.

Manipulating image position using CSS

Introduction

AI generated CSS3
CSS3 Image generated by AI

Centering an image alongside text in a container might seem straightforward, but dealing with the differences between block and inline elements often complicates the task. While it's easy to position these elements separately, combining them can present unexpected challenges. I recently encountered this issue when trying to align an image to the left and center the heading within the same container. After experimenting with outdated techniques like float, I turned to modern CSS tools like Flexbox to find a cleaner, more efficient solution. This article explores my approach and the lessons learned along the way.

The challenge

The text I was using was my site heading with the HTML element <h1. which is a block-level element, which means that it doesn't play nice with other elements on the same line. It starts on a new line and needs to take up the entire line. Whereas the <img> element is an inline element which means it is happy to play with others on the same line. Of course, I could play with the CSS float property, but there is a problem. The moment I introduce float for an element, it takes that element out of the document flow. and it becomes hard to control the behavior of that element.

Options

As mentioned earlier, I tried using float: left and float: inline-start, but it doesn't always behave as I want. As a best practice, I try to use the latest techniques as much as possible and that's where the modern flex and CSS GridBox came in. Flexbox when assigned to the parent container, aligns all the content text to the left as shown below.

Text aligned to left with just flex
Text aligned to left with just flex

After a lot of trial and error, it came down to using specificity and going minimalist. I also wanted to have the option to style images that I might use on the site independently so I didn't apply any styling to the core img element. I created several classes to manipulate the images and applied those. During all this trial and error, another problem vexed me. I couldn't get the image to align to the middle of the parent container with all the techniques I knew. I researched and tried with align-self property. That finally worked. I didn't want to apply this to the core img element and I didn't want to create a class for this so I used the Child Combinator to target the specific img element which is a child of header element (header > img). That took care of the issue of image alignment.

The next issue was to align the header text in the center. I tried all the tricks I knew with text-align, align-self, align-items, justify-self, and justify-items. But because the parent header element was marked as flex, the subsequent styles didn't apply. Finally I tried a simple trick to center the content using margin: auto and that did the trick. Here's how the final output looks now.

This is the final result
This is the final result

Even when I change the height of the header container, the image and text are vertically in the middle of the element and stay where there on the x-axis.

Final code

HTML code:

<header class="flexi">
  <img class="round-img small" src="img/Mukul-2019.jpg" alt="Mukul Dharwadkar" caption="Picture of Mukul Dharwadkar" />
  <h1 class="center-align">
    Mukul Dharwadkar
  </h1>
</header>

CSS code:

header {
    width: 900px;
    margin: auto;
    height: 120px;
    background-color: antiquewhite;
  }

/* The CSS rule below is highly specific for an img element that is a child of the header element.
Typically there will be only one img element inside the header and therefore this is safe to keep */

header > img {
  align-self: center;
}

.flexi {
  display: flex;
}

.round-img {
  border-radius: 50%;
}

.small {
  width: 100px;
}

.flexi {
  display: flex;
}

.center-align {
  margin: auto;
}

The full code is on my Github repo. Feel free to use it.

Conclusion:

Achieving the perfect alignment of images and text in web design often requires experimenting with different CSS techniques. In this case, Flexbox proved to be the most efficient and modern solution for centering content within a container, while maintaining the flexibility to adjust styling independently. By using targeted selectors like the Child Combinator and leveraging Flexbox’s alignment properties, I was able to solve the issue cleanly and efficiently. This method not only streamlines the code but also ensures that future adjustments will be easier to manage. CSS can be tricky, but with the right approach, you can create polished, professional layouts.

Powered by AI

Introduction

AI
AI
Artificial Intelligence (AI) is the current buzz word in every industry, not only IT. No conversation between IT or business leader nowadays is complete with a reference to AI or ML. One would be not mistaken to think that AI is the magical wand that is going to solve all our problems. Or is it? This is my take on the use (or overuse / abuse) of the term AI in today's conversations and marketing. What does true AI mean? Where does the intelligence come in? How do you define intelligence? Does the word intelligence mean true intelligence which can somehow mimic human-like brain and decisions or does it mean intelligence like a lot of information as used in government agencies?

True AI, or artificial general intelligence (AGI), implies the ability to mimic human reasoning, adapt to new situations, and solve problems in a wide range of contexts. However, most of what is referred to as AI today is more about automation, data-driven decision-making, and machine learning, which excels in specific tasks but lacks the holistic understanding that real intelligence implies. Whereas Machine Learning (ML) focuses on algorithms that learn from data without being explicitly programmed

Powered by AI?

In more and more advertisements and marketing collateral are all peppered with AI, ML, Autonomous or some variant thereof. But when you think about it, what does powered by AI really means. So I decided to dive in. I examined three different services in some detail.

  • Workday's AI driven HR
  • Juniper's AI driven support
  • Duolingo's AI powered language learning

Workday's AI driven HR

When I looked at the services that Workday claims to provide by AI, the things that stood out for me were mostly around candidate matching, skills matching, and insights in several domains. Honestly, none of this is AI driven. All of this can be and has been done using analytics in spreadsheets on a smaller scale or BI tools for enterprise scale. As far as the matching is concerned, I believe it is more miss than hit. The beauty of humans is the unpredictability and un-repeatability. That's why there term human error is common place. It is very rare for a human to repeat the previous step / action exactly.

The other areas that the AI falls short is decision support system or sentiment analysis. Making decision based on varying data points is not easy. Humans can do it very easily but machines fail spectacularly in actually helping with decisions or sentiment analysis. The point I am trying to make is that everyone writes their resume and skills differently. Companies have tried to standardize it by providing drop down lists, but then they dilute it by allowing candidates to enter their skills as free text. This leads to a very low percentage of candidate matching using the Application Tracking System (ATS) which many companies use to screen resumes automatically.

Juniper's AI driven support

Being from IT operations background, this is was more interesting to me than the others. I read through Juniper's solution briefs, web pages as much as I could without giving my email or contact information and I came away a little disappointed. The whole pitch is based on analysis of past data and Juniper claims to use 9 years of reinforced learning to deliver that platform. I was looking for predictive detection and auto-heal / self heal of issues. All the solutions talked about were accurate troubleshooting help, quick deployments of solutions, and reduction of number of tickets by 90%. I admit 90% reduction in tickets is impressive, but do you really need AI for that?

Around 12 years ago, I built tools to automatically heal application instances and server instance when certain conditions were fulfilled. Those were simple shell scripts that I wrote based on a few months of observation and interaction. I also updated those scripts to handle different scenarios. This reduced tickets from 150 a month to 10 tickets that were real issues.
During the same time, I built tools to deploy applications to over 100 servers within 15 minutes instead of over 4 hours that it used to take before my automation.

It was not AI. It was just pure and simple common sense and a desire to avoid manual labor as much as possible.

Duolingo's AI powered language learning

I admit that this is a service that I have explored the least. I am learning a language using Duolingo. Duolingo claims that if I subscribe to their Super plan, I get AI driven insights into my mistakes and guidance on what mistakes I made. I tried their Super plan as a trial and during that it told me what mistake I made and what I should have used instead. To be fair, the way they are using the data that they gather from a learner, is to personalize the learning experience by focusing on the student's weak areas and trying to emphasize and reinforce those concepts. Which again is more of ML than true AI. It would be better to use the data to tailor the subsequent lessons to focus on the weaker concepts rather than just during the review of lesson. Generally speaking when learning a new language, grammar is the most difficult part. Use the data collected during lessons to focus on the students weak areas.

Conclusion:

AI Hype
AI and its Hype
With the explosion of Gen AI in the past couple of years, everyone is trying to get on the AI bandwagon. But in my humble opinion, the word intelligence used here is not the cognition or understanding that we term intelligence as, but rather it is a lot of information and based on that the computer will make some decisions. It indeed reduces a lot of workload, but I would strongly urge to stop using AI in every conversation or marketing collateral but rather focus on actually solving the problems and make human lives better. According to a 2023 Gartner report, only 6% of companies using ‘AI-powered’ solutions are deploying what experts would classify as advanced machine learning or artificial intelligence. The graphic in the report is very illuminating with GenAI being at the top of "Peak of Inflated Expectations".

Using AI in everything creates unrealistic expectations in people's minds. Rather than contributing to the AI hype, companies should be transparent about what their technologies actually do — whether it's data-driven automation or advanced machine learning. This will help set realistic expectations and foster trust in the potential of AI to truly improve human lives in meaningful ways. The customer should also be aware of the marketing techniques and really try to peel the layers of the solution being sold to understand what they are buying.

Beautify your terminal with Zsh and themes

Introduction

Oh MysZsh
Oh my zsh logo

I have been using Linux for a better part of two decades now but I won't say that I am an expert in Linux. Till recently I didn't even know that I could change shells and what capabilities other shells have. I was more of a functional and casual user than anything else who knew how to get a few things done but didn't know the internals and the intricacies of the operating system. One of the intricacies is to change the default Linux shell from bash to zsh. Zsh is indeed a powerful shell with a lot of extensions, customizations, and community support with themes to tailor the shell to ones own preferences. That was another thing I didn't know that there are themes for shell. I always used to think that bash shell is what you have and you have to use it without complaints.

Well, I was wrong and there are a lot of things you can change about your terminal and in this blog post, I am going to show you how to do it. I know there are a lot of tutorials around the web that show you exactly this and some are good than others. But this is more for me than anybody else. Really. When I started working for AWS, I got an opportunity to meet very smart people who have extended the capabilities of software tools to the limit of what they can do and then some. That is one of the reasons why I wanted to learn more and more about the basics so that I can have a very solid foundation.

Why Zsh?

By installing a different shell, you can actually decorate and customize your plain old command prompt to something fancy very easily and you have a lot of options. Of course, you can also customize and decorate your command prompt on bash shell, but for that you need to know a lot of configurations and parameters if not all. And you need to build the configuration file by hand as well.

As I mentioned earlier, there is a vibrant and strong community support and there are several themes available along with pre-made scripts that you can just run and choose options to configure and tailor. That's what I plan to show you in this post.

Preparation

All the Linux installations come with a bash shell by default. Depending on the distribution you use, there might be other shells also installed but we need to ensure that Zsh is installed. I am using Ubuntu to show the commands. The steps remain the same on a Red Hat clone as well. Red Hat clones use yum to install and maintain software whereas Debian and its clones use apt.

  • Ensure you have Zsh installed. Run the following command.
    sudo apt install zsh -y

  • Check if the Zsh installation is done properly and where it is installed by running the following command.
    which zsh
    It should show an output something like /usr/bin/zsh

  • Now that Zsh is installed, change your default shell to zsh by running the following command.
    chsh -s $(which zsh)

Once the command runs successfully, you will need to restart your session in order for the terminal to start in the new shell environment. Once the terminal starts, the Zsh configuration scripts starts automatically to enable the user to configure settings and preferences. We are not going to do any configurations at this time and will leave this for later once all the compoents are installed and ready. On the prompt, select 0 to Exit and create a placeholder .zshrc file so that the script doesn't run again.
(0) Exit, creating the file ~/.zshrc containing just a comment.
That will prevent this function being run again.

Install and setup

Now we are getting to the good part. First of all we need to install and download Oh-my-zsh. Oh-My-Zsh is a delightful, open source, community-driven framework for managing your ZSH configuration. Download and run it using the either of the commands below. Generally all Linux installations come with wget preinstalled and you will have to install curl. Curl is a powerful tool for testing and debugging and if you don't have it, don't worry about it and don't bother downloading it. If you don't have it and don't know about it, you probably won't need it.

sh -c "$(wget https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
==OR==
sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

Next we need to make the terminal our own by installing and configuring themes. There are a lot of themes available for Zsh but my favorite is PowerLevel10K. To use it first clone the Powerlevel10K repo using git. If you don't have git, install it by running sudo apt install git -y on your terminal.

git clone https://github.com/romkatv/powerlevel10k.git $ZSH_CUSTOM/themes/powerlevel10k

Once this is done, we will clone auto-suggestions and syntax highlighting repos which are plugins which extend the capabilities of Zsh a lot. Again there a lot of plugins available for Oh-my-zsh but for now we will concentrate on these two.
git clone https://github.com/zsh-users/zsh-autosuggestions.git $ZSH_CUSTOM/plugins/zsh-autosuggestions
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git $ZSH_CUSTOM/plugins/zsh-syntax-highlighting

Now that all the major components are downloaded and ready, it's time to personalize, configure and make the terminal prompt our own. Open .zshrc (It's located in your home folder) file in your favorite editor and add the following lines
ZSH_THEME="powerlevel10k/powerlevel10k"
POWERLEVEL9K_MODE="nerdfont-complete"

Uncomment the options to activate them as needed. I suggest the following options.
CASE_SENSITIVE="true" # Around line 21
ENABLE_CORRECTION="true" # Around line 45

Finally add the plugins that we cloned earlier for autosuggestions and syntax-highlighting at around line 71
plugins=(git zsh-autosuggestions zsh-syntax-highlighting sudo)

Once done, save and exit. We are not done yet. We need to install a font that will allow us to use cool fonts and icons to spice up the prompt. The font that we need is Fira mono font. While the font itself is free from Google, we need the patched version. The entire fonts library is available on Github. The fonts library is a huge one and you don't really need all of those. I recommend downloading Firamono medium font from my [site](Internal link here) and install it. To install it, you can either double-click on the font file and open it in File manager and click install or create a .fonts directory in your home folder and copy it there. I recommend the first option to install it system wide.

Once installed configure Terminal app preferences --> Profile --> Text to use Fira mono font to harness the power of Oh-my-zsh

At this point you need to log out and log back in to ensure the changes made so far are persistent.

After logging back in launch Terminal. The P10K configure will run for the first time. Follow the prompts. My suggestions are:

  • For Prompt Style choose (3) Rainbow
  • For Character Set choose (1) Unicode
  • Choose whether you want to show current time on the command prompt or not. I suggest (3) 12 hour format
  • For prompt separators choose (1) Angled
  • For prompt heads choose (1) Sharp
  • For prompt tails choose (1) Flat
  • For prompt height choose (1) One line
  • For prompt spacing choose (1) Compact
  • For Icons you can choose your preference. I like a lot of fonts and I recommend (2) Many icons
  • For Prompt flow choose (1) Concise. Again this is a personal choice. I recommend Concise
    • Choose (y) to select Transient Prompt to provide a clean work area
  • Choose (2) to have Quiet Instant prompt mode
    Choose to save to .zshrc and apply the changes we just made.

And that's it. You are done! You now have a fantastic command prompt.

How to create a bootable USB drive on MacBook

Introduction

Bootable USB Drive for MacBook
Bootable USB Drive

I like to tinker around with technology. I think that much will be evident from my website and the type of posts that I write here. Sometime back, I was trying to play around with a Raspberry Pi. It was a RPi zero so it didn't have a lot of capabilities, but I figured out that I could run Raspbian Buster or Debian Buster on it and also ran this website on it till recently when I migrated it to AWS.

The problem

For quite some time now, I have been using a MacBook as my primary computer and while it is a great machine for personal productivity and development, I didn't really dive deep on system administration. I needed to figure out how to format a USB drive and a SD card on Mac and write a bootable image on it.

I did a lot of research and I came across a site from a Microsoft engineer who had written a very nice article on this and I used that article to very easily achieve my task. But I can't find that anymore so instead of relying on someone else, I thought I will document it myself and also add some additional details so that others can benefit. I could have very easily used my Windows computer but that wouldn't be integral with my tinkerer nature.

Recently, I had revived an old 2009 laptop which refused to run any of newer OSes so I did another research on possible OSes that could run on it and figured that I could run Lubuntu on it easily. So I went ahead and downloaded the latest version of Lubuntu - Focal Fossa and set to the task of creating a bootable USB drive.

The technical details

To achieve this, I would need only two tools from my MacBook

  1. diskutil
  2. dd

Let's take a look at the details now.

The first thing that needs to be done is to determine the device details of the USB drive. To do that, first insert the drive in your USB port and run the command below:

diskutil list

[image here]

This command will show an output of the disks mounted. Determine the device details by looking at the disk size and note down the device details which will be in the form /dev/diskN where N is a number. Once the device number is determined, run the following command under root (sudo) privileges

sudo diskutil eraseDisk FAT32 LABEL MBRFormat /dev/diskN

[image here]

Make sure to replace the LABEL with the name you want and N with the number noted above.

It will take a few minutes to complete the process and once it is complete, run the following command:

diskutil unmountDisk /dev/diskN

Again taking care to replace N with the appropriate number.

Once the disk is unmounted, we are now ready to write the bootable image to the USB drive. To do so, run the following command:

sudo dd bs=1m if=/Path/to/fileimage.iso of=/dev/diskN

[image here]
Depending on the size of the image, this can take several minutes. You can check the progress of the process by pressing Ctrl+T on the screen. Once finished, run the following command to eject the disk from the computer gracefully.

diskutil eject /dev/diskN

Don't forget to replace the N!

Even more deeper details

Now that you understand the commands, let's take a detailed look at the verbs and the switches we used in the commands.

  • diskutil: We used the following verbs with this command.

    • list: This option lists all the drives that are attached and mounted on the operating system
    • eraseDisk (note the capital D): This option will erase the disk that is provided as an option. It also takes the following arguments:
      • Filesystem Type: Choose from FAT32, NTFS, EXT4 etc.
      • Label: The name to be given to the disk
      • Format: The format type of the disk. Valid Values are: APM (Apple Partition map), GPT (GUID Partition Table) and MBR (Master Boot record). Using MBR will ensure that the drive will be bootable on non-Mac machines as well.
      • Device: The device number that we noted earlier.
        Note: This option needs sudo or root privileges to run.
    • unmountDisk: This will unmount the entire disk including all the volumes that may be present on the disk. It needs the device argument to work.
    • eject: This will eject the disk from the computer and make it safe for the removal media to be removed from the computer without the risk of data corruption.
  • MARKDOWN_HASH1aabac6d068eef6a7bad3fdf50a05cc8MARKDOWNHASH: **[dd](https://en.wikipedia.org/wiki/Dd(Unix))* stands for data duplicator* and is used to copy and transform data from one device to another. It is a low level Linux command line utility which will be a great addition in any system administrator's toolkit. We used the following verbs and switches in this exercise:

    • bs: Stands for block size. The default block size for dd utility is 512 bytes and there's not one right size for setting a block size. There is a good discussion here. This operand sets both the input block size and output block size to the desired value which I have used as 1 mb.
    • if: Denotes the input file where the dd should read from instead of standard input.
    • of: Denotes the destination where dd should write to instead of standard output.

Conclusion:

That's it for now. I hope this short tutorial has been helpful to you. Instead of using the GUI tools, I have found that using these command line utilities provide a lot of flexibility and power to the system administrator but can be confusing at times and have potential to destroy data if used incorrectly

Fix 404 errors in Laravel application authenication

Introduction

I am not a Laravel programmer by any stretch of imagination but I like to think that I am a pretty good engineer who can solve problems regardless of the situation. Recently I faced a situation where I was working with one of my clients on setting up a validated environment for their customers using a standard operating procedure (SOP). They had gotten the application developed from a third party vendor. Long story short, since their target market is highly regulated industry, they must have detailed and exhaustive documentation of all steps of setting up and tearing down of the computer systems.

It's not easy to write SOPs

There is a video where a father is asking his kids for some instructions to make a peanut butter and jelly sandwich. While for most of us making a peanut butter and jelly sandwich is highly intuitive and easy, for a person who doesn't know what is to be done, it can be stupefying. When we write instructions, we know a lot of things as we have already done the task successfully and assume that the reader is already aware of a lot of things that we know about and do not include those steps in our documentation.
Sometimes it is different. We miss some steps in our documentation and when someone else finds out and reports the gaps, we quickly make the change and do not update the documentation, hoping the problem will not recur. That exacerbates the actual problem. In case someone else has to reproduce the environment using those SOPs then they are basically stuck.
Therefore it is very important to have accurate and correct documentation updated. Admittedly, it is tedious and cumbersome, but in the long run it definitely is the right thing to do.

Laravel and Apache server

I had a Laravel application that was front ended by an Apache server and the backend was MySQL database. The developer had setup an instance and my job was to replicate the same using the SOP that they had built to ensure they are accurate and the process is predictable and repeatable. I followed the steps exactly as documented. I was able to get to the login screen. So far so good. But when I tried to log in using the test credentials, I was shown a 404 - Page not found error. Initially, I worked with the developer and they did something and fixed it. Interestingly, they failed to mention it to me what they did and neither was it documented in the SOP. So as it happened, I had to rebuild the server to try and get the SOP working. I again faced that issue and this time, instead of reaching out to the developer, I did my own research and Voila' I was able to solve the problem.
The research led me far and wide and in one of the forums I found the possible answer which suggested setting AllowOverride All on the parent directory of the application. In my case it was /var/www. The post suggested to set it at the main Apache configuration file. But I always try to localize these solutions and not make global changes if I can help it in order to keep the installation secure. So I added a Directory directive in the VHost configuration file which enables AllowOverride All for that VHost only while leaving the original configuration untouched.

Conclusion

They say that Laravel is a programming language for coding artists with elegant frameworks. But these issues are pretty simple and will be prevalent in all applications that require authentication. Why is this not better documented? More importantly why does the authenticate or login action in Laravel return a 404 error?
I wonder...

Verified by MonsterInsights