Rendering Markdown in WordPress without plugins by using Parsedown
Much of what is generated using GenAI as articles is output as Markdown, meaning that you need to convert the content when using it in a WordPress website. Naturally, this kind of thing should be done with care to ensure that you are the creator and that it is not all the work of a machine; orchestration is fine, regurgitation does that add that much. Naturally, fact checking is another need as well.
Writing plain Markdown has secured its own following as well, with WordPress plugins switching over the editor to facilitate such a mode of editing. When I tried Markup Markdown, I found it restrictive when it came to working with images within the text, and it needed a workaround for getting links to open in new browser tabs as well. Thus, I got rid of it to realise that it had not converted any Markdown as I expected, only to provide rendering at post or page display time. Rather than attempting to update the affected text, I decided to see if another solution could be found.
This took me to Parsedown, which proved to be handy for accomplishing what I needed once I had everything set in place. First, that meant cloning its GitHub repo onto the web server. Next, I created a directory called includes under that of my theme. Into there, I copied Parsedown.php to that location. When all was done, I ensured that file and directory ownership were assigned to www-data to avoid execution issues.
Then, I could set to updating the functions.php file. The first line to get added there included the parser file:
require_once get_template_directory() . '/includes/Parsedown.php';
After that, I found that I needed to disable the WordPress rendering machinery because that got in the way of Markdown rendering:
remove_filter('the_content', 'wpautop');
remove_filter('the_content', 'wptexturize');
The last step was to add a filter that parsed the Markdown and passed its output to WordPress rendering to do the rest as usual. This was a simple affair until I needed to deal with code snippets in pre and code tags. Hopefully, the included comments tell you much of what is happening. A possible exception is $matches[0]which itself is an array of entire <pre>...</pre> blocks including the containing tags, with $i => $block doing a $key (not the same variable as in the code, by the way) => $value lookup of the values in the array nesting.
add_filter('the_content', function($content) {
// Prepare a store for placeholders
$placeholders = [];
// 1. Extract pre blocks (including nested code) and replace with safe placeholders
preg_match_all('//si', $content, $pre_matches);
foreach ($pre_matches[0] as $i => $block) {
$key = "§PREBLOCK{$i}§";
$placeholders[$key] = $block;
$content = str_replace($block, $key, $content);
}
// 2. Extract standalone code blocks (not inside pre)
preg_match_all('/).*?<\/code>/si', $content, $code_matches);
foreach ($code_matches[0] as $i => $block) {
$key = "§CODEBLOCK{$i}§";
$placeholders[$key] = $block;
$content = str_replace($block, $key, $content);
}
// 3. Run Parsedown on the remaining content
$Parsedown = new Parsedown();
$content = $Parsedown->text($content);
// 4. Restore both pre and code placeholders
foreach ($placeholders as $key => $block) {
$content = str_replace($key, $block, $content);
}
// 5. Apply paragraph formatting
return wpautop($content);
}, 12);
All of this avoided dealing with extra plugins to produce the required result. Handily, I still use the Classic Editor, which makes this work a lot more easily. There still is a Markdown import plugin that I am tempted to remove as well to streamline things. That can wait, though. It best not add any more of them any way, not least avoid clashes between them and what is now in the theme.
Building an email summariser for Apple Mail using both OpenAI and Shortcuts
One thing that I am finding useful in Outlook is the ability to summarise emails using Copilot, especially for those that I do not need to read in full. While Apple Mail does have something similar, I find it to be very terse in comparison. Thus, I started to wonder about just that by using the OpenAI API and the Apple Shortcuts app. All that follows applies to macOS Sequoia, though the Tahoe version is with us too.
Prerequisite
While you can have the required OpenAI API key declared within the Shortcut, that is a poor practice from a security point of view. Thus, you will need this to be stored in the macOS keychain, which can be accomplished within a Terminal session and issuing a command like the following:
security add-generic-password -a openai -s openai_api_key -w [API Key]
In the command above, you need to add the actual API key before executing it to ensure that it is available to the steps that follow. To check that all is in order, issue the following command to see the API key again:
security find-generic-password -a openai -s openai_api_key -w
This process also allows you to rotate credentials without editing the workflow, allowing for a change of API keys should that ever be needed.
Building the Shortcut
With the API safely stored, we can move onto the actual steps involved in setting up the Email Summarisation Shortcut that we need.
Step 1: Collect Selected Email Messages
First, open the Shortcuts app and create a new Shortcut. Then, add a Run AppleScript action and that contains the following code:
tell application "Mail"
set selectedMessages to selection
set collectedText to ""
repeat with msg in selectedMessages
set msgSubject to subject of msg
set msgBody to content of msg
set collectedText to collectedText & "Subject: " & msgSubject & return & msgBody & return & return
end repeat
end tell
return collectedText
This script loops through the selected Mail messages and combines their subjects and bodies into a single text block.
Step 2: Retrieve the API Key
Next, add a Run Shell Script action and paste this command:
security find-generic-password -a openai -s openai_api_key -w | tr -d 'n'
This reads the API key from the keychain and strips any trailing newline characters that could break the authentication header, the first of several gotchas that took me a while to sort.
Step 3: Send the Request to GPT-5
The, add a Get Contents of URL action and configure it as follows:
URL: https://api.openai.com/v1/chat/completions
Method: POST
Headers:
- Authorization:
Bearer [Shell Script result] - Content-Type:
application/json
Request Body (JSON):
{
"model": "gpt-5",
"temperature": 1,
"messages": [
{
"role": "system",
"content": "Summarise the following email(s) clearly and concisely."
},
{
"role": "user",
"content": "[AppleScript result]"
}
]
}
When this step is executed, it replaces [Shell Script result] with the output from Step 2, and [AppleScript result] with the output from Step 1. Here, GPT-5 only accepts a temperature value of 1 (a lower value would limit the variability in the output if it could be used), unlike other OpenAI models and what you may see documented elsewhere.
Step 4: Extract the Summary from the Response
The API returns a JSON response that you need to parse, an operation that differs according to the API; Anthropic Claude has a different structure, for example. To accomplish this for OpenAI's gateway, add these actions in sequence to replicate what is achieved using in Python by loading completion.choices[0].message.content:
- Get Dictionary from Input (converts the response to a dictionary)
- Get Dictionary Value for key "choices"
- Get Item from List (select item 1)
- Get Dictionary Value for key "message"
- Get Dictionary Value for key "content"
One all is done (and it took me a while to get that to happen because of the dictionary → list → dictionary → dictionary flow; figuring out that not everything in the nesting was a dictionary took some time), click the information button on this final action and rename it to Summary Text. This makes it easier to reference in later steps.
Step 5: Display the Summary
Add a Show action and select the Summary Text variable. This shows the generated summary in a window with Close and Share buttons. The latter allows you to send to output to applications like Notes or OneNote, but not to Pages or Word. In macOS Sequoia, the list is rather locked down, which means that you cannot extend it beyond the available options. In use or during setup testing, beware of losing the open summary window behind others if you move to another app because it is tricky to get back to without using the CTRL + UP keyboard shortcut to display all open windows at once.
Step 6: Copy to Clipboard
Given the aforementioned restrictions, there is a lot to be said for adding a Copy to Clipboard action with the Summary Text variable as input. This allows you to paste the summary immediately into other apps beyond those available using the Share facility.
Step 7: Return Focus to Mail
After all these, add another Run AppleScript action with this single line:
tell application "Mail" to activate
This brings the Mail app back to the front, which is particularly useful when you trigger the Shortcut via a keyboard shortcut or if you move to another app window.
Step 8: Make the New Shortcut Available for Use
Lastly, click the information button at the top of your Shortcut screen. One useful option that can be activated is the Pin in Menu Bar one, which adds a menu to the top bar with an entry for the new Email Summary Shortcut in there. Ticking the box for the Use as Quick Action option allows you to set a keyboard shortcut. Until, the menu bar option appealed to me, that did have its uses. You just have to ensure that what you select does not override any combination that is in use already. Handily, I also found icons for my Shortcuts in Launchpad as well, which means that they also could be added to the Dock, something that I also briefly did.
Using the Shortcut
After expending the effort needed to set it up, using the new email summariser is straightforward. In Apple Mail, select one or more messages that you want to summarise; there is no need to select and copy the contained textual content because the Shortcut does that for you. Using the previously assigned keyboard combination, menu or Launchpad icon then triggers the summarisation processing. Thus, a window appears moments later displaying the generated summary while the same text is copied to your clipboard, ready to paste anywhere you need it to go. When you dismiss the pop-up window, the Mail app then automatically comes back into focus again.
Comet and Atlas: Navigating the security risks of AI Browsers
The arrival of the ChatGPT Atlas browser from OpenAI on 21st October has lured me into some probing of its possibilities. While Perplexity may have launched its Comet browser first on 9th July, their tendency to put news under our noses in other places had turned me off them. It helps that the former is offered extra charge for ChatGPT users, while the latter comes with a free tier and an optional Plus subscription plan. My having a Mac means that I do not need to await Windows and mobile versions of Atlas, either.
Both aim to interpret pages, condense information and carry out small jobs that cut down the number of clicks. Atlas does so with a sidebar that can read multiple documents at once and an Agent Mode that can execute tasks in a semi-autonomous way, while Comet leans into shortcut commands that trigger compact workflows. However, both browsers are beset by security issues that give enough cause for concern that added wariness is in order.
In many ways, they appear to be solutions looking for problems to address. In Atlas, I found the Agent mode needed added guidance when checking the content of a personal website for gaps. Jobs can become too big for it, so they need everything broken down. Add in the security concerns mentioned below, and enthusiasm for seeing what they can do gets blunted. When you see Atlas adding threads to your main ChatGPT roster, that gives you a hint as to what is involved.
The Security Landscape
Both Comet and Atlas are susceptible to indirect prompt injection, where pages contain hidden instructions that the model follows without user awareness, and AI sidebar spoofing, where malicious sites create convincing copies of AI sidebars to direct users into compromising actions. Furthermore, demonstrations have included scenarios where attackers steal cryptocurrency and gain access to Gmail and Google Drive.
For instance, Brave's security team has described indirect prompt injection as a systemic challenge affecting the whole class of AI-augmented browsers. Similarly, Perplexity's security group has stated that the phenomenon demands rethinking security from the ground up. In a test involving 103 phishing attacks, Microsoft Edge blocked 53 percent and Google Chrome 47 percent, yet Comet blocked 7 percent and Atlas 5.8 percent.
Memory presents an additional attack surface because these tools retain information between sessions, and researchers have demonstrated that memory can be poisoned by carefully crafted content, with the taint persisting across sessions and devices if synchronisation is enabled. Shadow IT adoption has begun: within nine days of launch, 27.7 percent of enterprises had at least one Atlas download, with uptake in technology at 67 percent, pharmaceuticals at 50 percent and finance at 40 percent.
Mitigating the Risks
Sensibly, security practitioners recommend separating ordinary browsing from agentic browsing. Here, it helps that AI browsers are cut down items anyway, at least based on my experience of Atlas. Figuring out what you can do with them using public information in a read-only manner will be enough at this point. In any event, it is essential to keep them away from banking, health, personal accounts, credentials, payments and regulated data until security improves.
As one precaution, maintaining separate AI accounts could act as a boundary to contain potential compromises, though this does not address the underlying issue that prompt injection manipulates the agent's decision-making processes. With Atlas, disable Browser Memories and per-site visibility by default, with explicit opt-ins only on specific public sites. Additionally, use Agent Mode only when not logged into any accounts. Furthermore, do not import passwords or payment methods. With Comet, use narrowly scoped shortcuts that operate on public information and avoid workflows involving sign-ins, credentials or payments.
Small businesses can run limited pilots in non-sensitive areas with strict allow and deny lists, then reassess by mid-2026 as security hardens, while large enterprises should adopt a block-and-monitor stance while developing governance frameworks that anticipate safer releases in 2026 and 2027. In parallel, security teams should watch for circumvention attempts and prepare policies that separate public research from sensitive work, mandate safe defaults and prohibit connections to confidential systems. Finally, training is necessary because users need to understand the specific risks these browsers present.
How Competition Might Help
Established browser vendors are adding AI capabilities on top of existing security infrastructure. Chrome is integrating Gemini, and Edge is incorporating Copilot more tightly into the workflow. Meanwhile, Brave continues with a privacy-first stance through Leo, while Opera's Aria, Arc with Dia and SigmaOS reflect different approaches. Current projections suggest that major browsers will introduce safer AI features in the final quarter of 2025, that the first enterprise-ready capabilities will arrive in the first half of 2026 and that by 2027 AI-assisted browsing will be standard and broadly secure.
Competition from Chrome and Edge will drive AI assistance into more established security frameworks, while standalone AI browsers will work to address their security gaps. Mitigations for prompt injection and sidebar spoofing will likely involve layered approaches combining detection, containment and improved user interface signals. Until then, Comet and Atlas can provide productivity benefits in public-facing work and research, but their security posture is not suitable for sensitive tasks. Use the tools where the risk is acceptable, keep sensitive work in conventional browsers, and anticipate that safer versions will become standard over the next two years.
AI infrastructure under pressure: Outages, power demands and the race for resilience
The past few weeks brought a clear message from across the AI landscape: adoption is racing ahead, while the underlying infrastructure is working hard to keep up. A pair of major cloud outages in October offered a stark stress test, exposing just how deeply AI has become woven into daily services.
At the same time, there were significant shifts in hardware strategy, a wave of new tools for developers and creators and a changing playbook for how information is found online. There is progress on resilience and efficiency, yet the system is still bending under demand. Understanding where it held, where it creaked and where it is being reinforced sets the scene for what comes next.
Infrastructure Stress and Outages
The outages dominated early discussion. An AWS incident that lasted around 15 hours and disrupted more than a thousand services was followed nine days later by a global Azure failure. Each cascaded across systems that depend on them, illustrating how AI now amplifies the consequences of platform problems.
This was less about a single point of failure and more about the growing blast radius when connected services falter. The effect on productivity was visible too: a separate 10-hour ChatGPT downtime showed how fast outages of core AI tools now translate into lost work time.
Power Demand and Grid Strain
Behind the headlines sits a larger story about electricity, grids and planning. Data centres accounted for roughly 4% of US electricity use in 2024, about 183 TWh and the International Energy Agency projects around 945 TWh by 2030, with AI as a principal driver.
The averages conceal stark local effects. Wholesale prices near dense clusters have spiked by as much as 267% at times, household bills are rising by about $16–$18 per month in affected areas and capacity prices in the PJM market jumped from $28.92 per megawatt to $329.17. The US grid faces an upgrade bill of about $720 billion by 2030, yet permitting and build timelines are long, creating a bottleneck just as demand accelerates.
Technical Grid Issues
Technical realities on the grid add another layer of challenge. Fast load swings from AI clusters, harmonic distortions and degraded power quality are no longer theoretical concerns. A Virginia incident in which 60 data centres disconnected simultaneously did not trigger a collapse but did reveal the fragility introduced by concentrated high-performance compute.
Security and New Failure Modes
Security risks are evolving in parallel. Agentic systems that can plan, reason and call tools open new failure modes. AI-enabled spear phishing appears to be 350% more effective than traditional attempts and could be 50 times more profitable, a worrying backdrop when outages already have a clear link to lost productivity.
Security considerations now reach into the tools people use to access AI as well. New AI browsers attract attention, and with that comes scrutiny. OpenAI's Atlas and Perplexity's Comet launched with promising features, yet researchers flagged critical issues.
Comet is vulnerable to "CometJacking", a malicious URL hijack that enables data theft, while Atlas suffered a cross-site request forgery weakness that allowed persistent code injection into ChatGPT memory. Both products have been noted for assertive data collection.
Caution and good hygiene are prudent until the fixes and policies settle. It is a reminder that the convenience of integrating models directly into browsing comes with a new attack surface.
Efficiency and Mitigation Strategies
Industry responses are gathering pace. Efficiency remains the first lever. Hyperscalers now report power usage effectiveness around 1.08 to 1.09, compared with more typical figures of 1.5 to 1.6. Direct chip cooling can cut energy needs by up to 40%.
Grid-interactive operations and more work at the edge offer ways to smooth demand and reduce concentration risk, while new power partnerships hint at longer-term change. Microsoft's agreement with Constellation on nuclear power is one example of how compute providers are thinking beyond incremental efficiency gains.
An emerging pattern is becoming visible through these efforts. Proactive regional planning and rapid efficiency improvements could allow computational output to grow by an order of magnitude, while power use merely doubles. More distributed architectures are being explored to reduce the hazard of over-concentration.
A realistic outlook sets data centres at around 3% of global electricity use by 2030, which is notable but still smaller than anticipated growth from electric vehicles or air conditioning. If the $720 billion in grid investment materialises, it could add around 120 GW of capacity by 2030, as much as half of which would be absorbed by data centres. The resilience gap is real, but it appears to be narrowing, provided the sector moves quickly to apply lessons from each failure.
Regional and Policy Responses
Regional policies are starting to encourage resilience too. Oregon's POWER Act asks operators to contribute to grid robustness, Singapore's tight focus on efficiency has delivered around a 30% power reduction even as capacity expands and a moratorium in Dublin has pushed growth into more distributed build-outs. On the U.S. federal government side, the Department of Homeland Security updated frameworks after a 2024 watchdog warning, with AI risk programmes now in place for 15 of the 16 critical infrastructure sectors.
Hardware Competition and Strategy
Competition is sharpening. Anthropic deepened its partnership with Google Cloud to train on TPUs, a move that challenges Nvidia's dominance and signals a broader rebalancing in AI hardware. Nvidia's chief executive has acknowledged TPUs as robust competition.
Another fresh entry came from Extropic, which unveiled thermodynamic sampling units, a probabilistic chip design that claims up to 10,000-fold lower energy use than GPUs for AI workloads. Development kits are shipping and a Z-1 chip is planned for next year, yet as with any radical architecture, proof at scale will take time.
Nvidia, meanwhile, presented an ambitious outlook, targeting $500 billion in chip revenue by 2026 through its Blackwell and Rubin lines. The US Department of Energy plans seven supercomputers comprising more than 100,000 Blackwell GPUs and the company announced partnerships spanning pharmaceuticals, industrials and consumer platforms.
A $1 billion investment in Nokia hints at the importance of AI-centric networks. New open-source models and datasets accompanied the announcements, and the company's share price surged to a record.
Corporate Restructuring
Corporate strategy and hardware choices also entered a new phase. OpenAI completed its restructuring into a public benefit corporation, with a rebranded OpenAI Foundation holding around $130 billion in equity and allocating $25 billion to health and AI resilience. Microsoft's stake now sits at about 27% and is worth roughly $135 billion, with technology rights retained through 2032. Both parties have scope to work with other partners. OpenAI committed around $250 billion to Azure yet retains the ability to use other compute providers. An independent panel will verify claims of artificial general intelligence, an unusual governance step that will be watched closely.
Search and Discovery Evolution
Away from infrastructure, the way audiences find and trust information is shifting. Search is moving from the old aim of ranking for clicks to answer engine optimisation, where the goal is to be quoted by systems such as ChatGPT, Claude or Perplexity.
The numbers explain why. Google handled more than five trillion queries in 2024, while generative platforms now process around 37.5 million prompt-like searches per day. Google's AI Overviews, which surface summary answers above organic results, have reshaped click behaviour.
Independent analyses report top-ranking pages seeing click-through rates fall by roughly a third where Overviews appear, with some keywords faring worse, and a Pew study finds overall clicks on such results dropping from 15% to 8%. Zero-click searches rose from around 56% to 69% between May 2024 and May 2025.
Chegg's non-subscriber traffic fell by 49% in this period, part of an ongoing dispute with Google. Google counters that total engagement in covered queries has risen by about 10%. Whichever way that one reads the data, the direction is clear: visibility is less about rank position and more about being cited by a summarising engine.
In practice, that means structuring content, so a model can parse, trust and attribute it. Clear Q&A-style sections with direct answers, followed by context and cited evidence, help models extract usable statements. Schema markup for FAQs and how-to content improves machine readability.
Measuring success also changes. Traditional analytics rarely show when an LLM quotes a source, so teams are turning to tools that track citations in AI outputs and tying those to conversion quality, branded search volume and more in-depth engagement with pricing or documentation. It is not a replacement for SEO so much as a layer that reinforces it in an AI-first environment.
Developer Tools and Agentic Workflows
On the tools front, developers saw an acceleration in agent-centred workflows. Cursor launched its first in-house coding model, Composer, which aims for near-frontier quality while generating code around four times faster, often in under 30 seconds.
The broader Cursor 2.0 update added multi-agent capabilities, with as many as eight assistants able to work in parallel, alongside browsing, a test browser and voice controls. The direction of travel is away from single-shot completions and towards orchestration and review. Tutorials are following suit, demonstrating how to scaffold tasks such as a Next.js to-do application using planning files, parallel agent tasks and quick integration, with voice prompts in the loop.
Open-source and enterprise ecosystems continue to expand. GitHub introduced Agent HQ for coordinating coding agents, Google released Pomelli to generate marketing campaigns and IBM's Granite 4.0 Nano models brought larger on-device options in the 350 million to 1.5 billion parameter range.
FlowithOS reported strong scores on agentic web tasks, while Mozilla announced an open speech dataset initiative, and Kilo Code, Hailuo 2.3 and other projects broadened choice across coding and video. Grammarly rebranded as Superhuman, adding "Superhuman Go" agents to speed up writing tasks.
Creative Tools and Partnerships
Creative workflows are evolving quickly, too. Adobe used its MAX event to add AI assistants to Photoshop and Express, previewed an agent called Project Moonlight, and upgraded Firefly with conversational "Prompt to Edit" controls, custom image models and new video features including soundtracks and voiceovers. Partnerships mean Gemini, Veo and Imagen will sit inside Adobe tools, and Premiere's editing capabilities now extend to YouTube Shorts.
Figma acquired Weavy and rebranded it as Figma Weave for richer creative collaboration, and Canva unveiled its own foundation "Design Model" alongside a Creative Operating System meant to produce fully editable, AI-generated designs. New Canva features take in a revised video suite, forms, data connectors, email design, a 3D generator and an ad creation and performance tool called Grow, while Affinity is relaunching as a free, integrated professional app. Other entrants are trying to blend model strengths: one agent was trailed with Sora 2 clip stitching, Veo 3.1 visuals and multimodel blending for faster design output.
Music rights and AI found a new footing. Universal Music Group settled a lawsuit with Udio, the AI music generator, and the two will form a joint venture to launch a licensed platform in 2026. Artists who opt in will be paid both for training models on their catalogues and for remixes. Udio disabled song downloads following the deal, which annoyed some users, and UMG also announced a "responsible AI" alliance with Stability AI to build tools for artists. These arrangements suggest a path towards sanctioned use of style and catalogue, with compensation built in from the start.
Research and Introspection
Research and science updates added depth. Anthropic reported that its Claude system shows limited introspection, detecting planted concepts only about 20% of the time, separating injected "thoughts" from text and modulating its internal focus. That highlights both the promise and limits of transparency techniques, and the potential for models to conceal or fail to surface certain internal states.
UC Berkeley researchers demonstrated an AI-driven load balancing algorithm with around 30% efficiency improvements, a result that could ripple through cloud performance. IBM ran quantum algorithms on AMD FPGAs, pointing to progress in hybrid quantum-classical systems.
OpenAI launched an AI-integrated web browser positioned as a challenger to incumbents, Perplexity released a natural-language patents search and OpenAI's Aardvark, a GPT-5-based security agent, entered private beta.
Anthropic opened a Tokyo office and signed a cooperation pact with Japan's AI Safety Institute. Tether released QVAC Genesis I, a large open STEM dataset of more than one million data points and a local workbench app aimed at making development more private and less dependent on big platforms.
Age Restrictions and Policy
Meanwhile, policy considerations are reaching consumer platforms. Character AI will restrict users under 18 from open-ended chatbot conversations from late November, replacing them with creative tools and adding behaviour-based age detection, a response to pressure and proposals such as the GUARD Act.
Takeaways
Put together, the picture is one of rapid interdependence and swift correction. The infrastructure is not breaking, but it is being stretched, and recent failures have usefully mapped the weak points. If the sector continues to learn quickly from its own missteps, the resilience gap will continue to narrow, and the next round of outages will be less disruptive than the last.
Investment is flowing into grids and cooling, policy is nudging towards resilience, and compute providers are hedging hardware bets by searching for efficiency and supply assurance. On the application layer, agents are becoming a primary interface for work, creative tools are converging around editability and control, and discovery is shifting towards being quoted by machines rather than clicked by humans.
Security lapses at the interface are a reminder that novelty often arrives before maturity. The most likely path from here is uneven but forward: data centre power may rise, yet efficiency and distribution can blunt the impact; answer engines may compress clicks, yet they can send higher intent visitors to clear, well-structured sources; hardware competition may fragment the stack, yet it can also reduce concentration risk.
WARNING: Unsupported device 'SVG' for RTF destination. Using default device 'EMF'.
When running SAS programs to create some outputs, I met with the above message because I was sending output to an RTF file and these do not support SVG files. This must be a system default because there was nothing in the programs that should trigger the warning. Getting rid of the message uses one of two approaches:
goptions device=emf;
ods graphics / outputfmt=emf;
The first applies to legacy code generating graphics using SAS/GRAPH procedures, while the second is for the more modern ODS graphics procedures like SGPLOT. While the first one may work in all cases, that cannot be assumed without further testing.
There was another curiosity in this: the system setup should have taken care of things to stop the warning from appearing in the log. However, these were programs created from SAS logs to capture all code generated by any SAS macros that were called for regulatory submission purposes. Thus, they were self-contained and missed off the environment setup code, which is how I came to see what I did.
Remote access between Mac and Linux, Part 3: SSH, RDP and TigerVNC
This is Part 3 of a three-part series on connecting a Mac to a Linux Mint desktop. Part 1 introduced the available options, whilst Part 2 covered x11vnc for sharing physical desktops.
Whilst x11vnc excels at sharing an existing desktop, many scenarios call for terminal access or a fresh graphical session. This article examines three alternatives: SSH for command-line work, RDP for responsive remote desktops with Xfce, and TigerVNC for virtual Cinnamon sessions.
Terminal Access via SSH
For many administrative tasks, a secure shell session is enough. On the Linux machine, the OpenSSH server needs to be installed and running. On Debian or Ubuntu-based systems, including Linux Mint, the required packages are available with standard tools.
Installing with sudo apt install openssh-server followed by enabling the service with sudo systemctl enable ssh and starting it with sudo systemctl start ssh is all that is needed. The machine's address on the local network can be identified with ip addr show, and it is the entry under inet for the active interface that will be used.
From the Mac, a terminal session to that address is opened with a command of the form ssh username@192.168.1.xxx and this yields a full shell on the Linux machine without further configuration. On a home network, there is no need for router changes and SSH requires no extra client software on macOS.
SSH forms the foundation for secure operations beyond terminal access. It enables file transfer via scp and rsync, and can be used to create encrypted tunnels for other protocols when access from outside the local network is required.
RDP for New Desktop Sessions
Remote Desktop Protocol creates a new login session on the Linux machine and tends to feel smoother over imperfect links. On Linux Mint with Cinnamon, RDP is often the more responsive choice on a Mac, but Cinnamon's reliance on 3D compositing means xrdp does not work with it reliably. The usual workaround is to keep Cinnamon for local use and install a lightweight desktop specifically for remote sessions. Xfce works well in this role.
Setting Up xrdp with Xfce
After updating the package list, install xrdp with sudo apt install xrdp, set it to start automatically with sudo systemctl enable xrdp, and start it with sudo systemctl start xrdp. If a lightweight environment is not already available, install Xfce with sudo apt install xfce4, then tell xrdp to use it by creating a simple session file for the user account with echo "startxfce4" > ~/.xsession. Restarting the service with sudo systemctl restart xrdp completes the server side.
The Linux machine's IP address can be checked again so it can be entered into Microsoft Remote Desktop, which is a free download from the Mac App Store. Adding a new connection with the Linux IP and the user's credentials often suffices, and the first connection may present a certificate prompt that can be accepted.
RDP uses port 3389 by default, which needs no router configuration on the same network. It creates a new session rather than attaching to the one already shown on the Linux monitor, so it is not a means to view the live Cinnamon desktop, but performance is typically smooth and latency is well handled.
Why RDP with Xfce?
It is common for xrdp on Ubuntu-based distributions to select a simpler session type unless the user instructs it otherwise, which is why the small .xsession file pointing to Xfce helps. The combination of RDP's protocol efficiency and Xfce's lightweight nature delivers the most responsive experience for new sessions. The protocol translates keyboard and mouse input in a way that many clients have optimised for years, making it the most forgiving route when precise input behaviour matters. The trade-off is that what is shown is a separate desktop session, which can be a benefit or a drawback depending on the task.
TigerVNC for New Cinnamon Sessions
Those who want to keep Cinnamon for remote use can do so with a VNC server that creates a new virtual desktop. TigerVNC is a common choice on Linux Mint. Installing tigervnc-standalone-server, setting a password with vncpasswd and creating an xstartup file under ~/.vnc that launches Cinnamon will provide a new session for each connection.
Configuring TigerVNC
A minimal xstartup for Cinnamon sets the environment to X11, establishes the correct session variables and starts cinnamon-session. Making this file executable and then launching vncserver :1 starts a VNC server on port 5901. The server can be stopped later with vncserver -kill :1.
The xstartup script determines what desktop environment a virtual session launches, and setting the environment variables to Cinnamon then starting cinnamon-session is enough to present the expected desktop. Marking that startup file as executable is easy to miss, and it is required for TigerVNC to run it.
From the Mac, the built-in Screen Sharing app can be used from Finder's Connect to Server entry by supplying vnc://192.168.1.xxx:5901, or a third-party viewer such as RealVNC Viewer can connect to the same address and port. This approach provides the Cinnamon look and feel, though it can be less responsive than RDP when the network is not ideal, and it also creates a new desktop session rather than sharing the one already in use on the Linux screen.
Clipboard Support in TigerVNC
For TigerVNC, clipboard support typically requires the vncconfig helper application to be running on the server. Starting vncconfig -nowin & in the background, often by adding it to the ~/.vnc/xstartup file, enables clipboard synchronisation between the VNC client and server for plain text.
File Transfer
File transfer between the machines is best handled using the command-line tools that accompany SSH. On macOS, scp file.txt username@192.168.1.xxx:/home/username/ sends a file to Linux and scp username@192.168.1.xxx:/home/username/file.txt ~/Desktop/ retrieves one, whilst rsync with -avz flags can be used for larger or incremental transfers.
These tools work reliably regardless of which remote access method is being used for interactive sessions. File copy-paste is not supported by VNC protocols, making scp and rsync the dependable choice for moving files between machines.
Operational Considerations
Port Management
Understanding port mappings helps avoid connection issues. VNC display numbers map directly to TCP ports, so :0 means 5900, :1 means 5901 and so on. RDP uses port 3389 by default. When connecting with viewers, supplying the address alone will use the default port for that protocol. If a specific port must be stated, use a single colon with the actual TCP port number.
First Connection Issues
If a connection fails unexpectedly, checking whether a server is listening with netstat can save time. On first-time connections to an RDP server, the client may display a certificate warning that can be accepted for home use.
Making Services Persistent
For regular use, enabling services at boot removes the need for manual intervention. Both xrdp and TigerVNC can be configured to start automatically, ensuring that remote access is available whenever the Linux machine is running. The systemd service approach described for x11vnc in Part 2 can be adapted for TigerVNC if automatic startup of virtual sessions is desired.
Security and Convenience
Security considerations in a home setting are straightforward. When both machines are on the same local network, there is no need to adjust router settings for any of these methods. If remote access from outside the home is required, port forwarding and additional protections would be needed.
SSH can be exposed with careful key-based authentication, RDP should be placed behind a VPN or an SSH tunnel, and VNC should not be left open to the internet without an encrypted wrapper. For purely local use, enabling the necessary services at boot or keeping a simple set of commands to hand often suffices.
xrdp can be enabled once and left to run in the background, so the Mac's Microsoft Remote Desktop app can connect whenever needed. This provides a consistent way to access a fresh Xfce session without affecting what is displayed on the Linux machine's monitor.
Summary and Recommendations
The choice between these methods ultimately comes down to the specific use case. SSH provides everything necessary for administrative work and forms the foundation for secure file transfer. RDP into an Xfce session is a sensible choice when responsiveness and clean input handling are the priorities and a separate desktop is acceptable. TigerVNC can launch a full Cinnamon session for those who value continuity with the local environment and do not mind the slight loss of responsiveness that can accompany VNC.
For file transfer, the command-line tools that accompany SSH remain the most reliable route. Clipboard synchronisation for plain text is available in each approach, though TigerVNC typically needs vncconfig running on the server to enable it.
Having these options at hand allows a Mac and a Linux Mint desktop to work together smoothly on a home network. The setup is not onerous, and once a choice is made and the few necessary commands are learned, the connection can become an ordinary part of using the machines. After that, the day-to-day experience can be as simple as opening a single app on the Mac, clicking a saved connection and carrying on from where the Linux machine last left off.
The Complete Picture
Across this three-part series, we have examined the full range of remote access options between Mac and Linux:
- Part 1 provided the decision framework for choosing between terminal access, new desktop sessions and sharing physical displays.
- Part 2 explored x11vnc in detail, including performance tuning, input handling with KVM switches, clipboard troubleshooting and systemd service configuration.
- Part 3 covered SSH for terminal access, RDP with Xfce for responsive remote sessions, TigerVNC for virtual Cinnamon desktops, and file transfer considerations.
Each approach has its place, and understanding the trade-offs allows the right tool to be selected for the task at hand.
Remote access between Mac and Linux, Part 2: x11vnc for sharing physical desktops
This is Part 2 of a three-part series on connecting a Mac to a Linux Mint desktop. Part 1 introduced the available options, whilst Part 3 covers SSH, RDP and TigerVNC.
Sharing the existing physical desktop is the point at which x11vnc enters the picture. Unlike a virtual VNC server, x11vnc mirrors the live X display, so what appears on the Linux monitor is precisely what is seen remotely. This is often preferred when a process or window must be observed without starting another session, making it particularly useful for monitoring ongoing work or guiding someone seated at the machine.
Basic Setup
Installing x11vnc and creating a password with x11vnc -storepasswd sets up the basics. The command x11vnc -usepw -display :0 -forever shares the current display on port 5900. The -display :0 flag is the important part, as :0 corresponds to the active physical display.
On the Mac side, RealVNC Viewer can connect by supplying either the IP address alone or the address with :5900 appended. If an "Invalid endpoint: port not correctly specified" error appears, it almost always stems from an incorrect address format. The correct forms are 192.168.1.50 or 192.168.1.50:5900, whereas entries such as 192.168.1.50::5900 or 192.168.1.50:0 will fail.
If there is any uncertainty about the port in use, running netstat -tlnp | grep x11vnc on the Linux machine should show a listener on 0.0.0.0:5900 when sharing display :0.
VNC display numbers map directly to TCP ports, so :0 means 5900, :1 means 5901 and so on. When connecting with RealVNC Viewer, supplying the address alone will use the default VNC port, which suits x11vnc on display :0. If a port must be stated, use a single colon with the actual TCP port, not the X display number.
Performance Tuning
Performance tuning is possible with a few additional options. x11vnc can enable a client-side pixel cache with -ncache 10, which asks the viewer to keep a set of off-screen image buffers for faster redraws, and -ncache_cr improves visible smoothness when windows are moved by encouraging copyrect updates.
When scrolling seems to stutter, -scrollcopyrect can help by detecting scroll operations and drawing them efficiently. -grabptr can improve pointer synchronisation between client and server.
On composited desktops, the X DAMAGE extension is used by default to hint at changed regions, but some compositing window managers interfere with this. In such cases, adding -noxdamage can avoid issues where updates are missed, at the cost of more conservative screen polling. x11vnc prints guidance about these behaviours on startup, which can be useful if any anomalies are encountered.
Input Handling and Mouse Button Issues
Input behaviour over VNC often differs from what is experienced when using the Linux machine directly. x11vnc does not carry the Mac's system preferences for the mouse or trackpad across the network; rather, it synthesises standard events that the X server interprets. As a result, wheel scrolling may feel different and the mapping of mouse buttons can be surprising.
When everything works perfectly whilst sitting at the Linux desktop but changes when connecting remotely, it is often because the events are being translated in a generic fashion over VNC. Trying a different viewer on the Mac sometimes improves matters because each has its own handling of input. The macOS Screen Sharing app, RealVNC Viewer and the TigerVNC viewer are all viable choices, and they do not feel identical in use.
Working with KVM Switches
It is also worth noting that hardware in the path can alter what the Linux system receives. If a mouse is connected through a KVM switch, the KVM may re-encode USB Human Interface Device signals, so the computer sees a very generic device. This can remap buttons in unexpected ways, and that mapping can differ between direct local use and a VNC session that injects events at another layer.
One way to correct misassigned buttons is to ask x11vnc to translate them with -buttonmap, which swaps the first three buttons so that the injected events match what the desktop expects. In simple cases where, for example, middle and right are reversed, -buttonmap 132 can restore the standard order of left, middle and right.
However, when a device presents right-click as a higher-numbered button such as 8, x11vnc's simple mapping is not enough because it only translates the primary trio. In that situation, the correction needs to happen before x11vnc sees the events by using the X input system.
The xinput list command identifies the device and xinput get-button-map shows how its buttons are currently enumerated. Applying xinput set-button-map with the desired sequence, for instance xinput set-button-map <ID> 1 2 3 4 5 6 7 8 9, forces the right codes at the operating system level and resolves the issue for both local and remote sessions.
These changes are not persistent across reboots unless added to a startup script or represented with an Xorg configuration snippet. Alternatively, some KVM models offer a HID pass-through or transparent mode that preserves the device's native signalling, which avoids the need for remapping. Bypassing the KVM for the mouse entirely is another way to ensure that the Linux machine sees the expected button layout, though this obviously changes how inputs are switched between computers.
Even after addressing button mapping, some scrolling oddities can remain. Combining the caching options with -scrollcopyrect and -grabptr, then reconnecting with a different viewer if needed, often gets closer to the feel of direct use.
Clipboard Support
Clipboard support is an area that can require attention depending on the tools in use. With x11vnc, clipboard synchronisation for plain text is supported, though the exact behaviour and configuration requirements can vary. Version 0.9.16 includes clipboard functionality, but in practice users often report difficulties with bidirectional clipboard operation and may need to use helper utilities such as autocutsel to achieve reliable two-way clipboard sharing.
When properly configured, copying text on one side and pasting on the other should work in both directions, with Command-C and Command-V on macOS and the usual Ctrl shortcuts on Linux. Rich text and images are not transferred, and file copy-paste is not supported by x11vnc at all.
Troubleshooting Clipboard Synchronisation
If clipboard synchronisation is not working as expected, x11vnc can be run with verbose logging to observe clipboard events in real time. This is particularly useful for diagnosing whether clipboard data are being detected and transferred between the client and server.
To enable detailed logging, first stop any running x11vnc service:
sudo systemctl stop x11vnc.service
Then start x11vnc manually in a terminal with increased verbosity:
x11vnc -usepw -display :0 -forever -verbose -o ~/x11vnc_debug.log
Connect from the Mac via RealVNC Viewer and attempt to copy and paste text in both directions. In another terminal, monitor the log file:
tail -f ~/x11vnc_debug.log
Lines mentioning clipboard activity, such as "Clipboard: received new client data" or "Clipboard: sending text selection to X server", indicate that x11vnc is detecting and transferring clipboard changes. If no clipboard-related messages appear, the VNC viewer may have clipboard synchronisation disabled in its settings.
In RealVNC Viewer, check Preferences → Inputs to ensure that clipboard synchronisation is enabled. On the macOS Screen Sharing app, clipboard support should work by default when connecting to x11vnc.
After testing, stop the manual run with Ctrl+C and restart the service:
sudo systemctl start x11vnc.service
To make verbose logging permanent, the -verbose flag and log output option can be added to the systemd service file's ExecStart line, allowing clipboard activity to be monitored in /var/log/x11vnc.log at any time.
Running x11vnc as a System Service
If x11vnc is to be used regularly and convenience matters, it can be launched at startup as a service so that a remote viewer can connect without a preparatory shell session on the Linux side. Setting up x11vnc as a systemd service ensures that the VNC server starts automatically whenever the Linux Mint machine boots, even before logging in.
To create the service, run:
sudo nano /etc/systemd/system/x11vnc.service
Then paste the following configuration, adjusting the username as needed:
[Unit]
Description=Start x11vnc at startup for user
After=display-manager.service
Requires=display-manager.service
[Service]
Type=simple
ExecStart=/usr/bin/x11vnc -auth guess -forever -loop -noxdamage
-display :0 -rfbauth /home/user/.vnc/passwd
-buttonmap 138456729 -ncache 10 -ncache_cr
-o /var/log/x11vnc.log
User=user
Group=user
Restart=on-failure
[Install]
WantedBy=multi-user.target
The -auth guess option allows x11vnc to attach to the X session automatically, whilst logging is directed to /var/log/x11vnc.log. The button mapping and caching options shown here incorporate the performance tuning and input corrections discussed earlier. Adjust /home/user and the username to match the account in use.
After creating the service file, reload systemd and enable the service:
sudo systemctl daemon-reload
sudo systemctl enable x11vnc.service
sudo systemctl start x11vnc.service
Check that the service is running:
systemctl status x11vnc.service
The output should show Active: active (running). Once this is in place, the Mac can connect to vnc://<linux-ip>:5900 at any time, even immediately after the Linux machine boots.
If x11vnc does not reconnect cleanly after logging out of Cinnamon, adding ExecStopPost=/bin/sleep 2 to the [Service] section will give systemd a brief pause before restarting the service.
Security Considerations
Security considerations in a home setting are straightforward. When both machines are on the same local network, there is no need to adjust router settings. If remote access from outside the home is required, port forwarding and additional protections would be needed.
VNC should not be left open to the internet without an encrypted wrapper. For purely local use, enabling the service at boot as described above often suffices, but for external access an SSH tunnel or VPN is essential.
Conclusion
x11vnc provides a reliable way to share the physical desktop of a Linux machine with a Mac on the same network. The setup is not onerous, and once the service is configured and the few necessary commands are learned, the connection can become an ordinary part of using the machines. The pitfalls that appear repeatedly, such as port format in viewer applications, clipboard configuration, or the way KVM switches can alter mouse behaviour, are easily overcome with a little attention to detail.
In Part 3, we examine the alternatives: SSH for terminal access, RDP with Xfce for responsive remote desktop sessions, and TigerVNC for virtual Cinnamon desktops.
Remote access between Mac and Linux: Choosing the right approach
Connecting from a Mac to a Linux desktop on the same network can be done in several ways, and the right choice depends on whether terminal access suffices or a full graphical session is needed. Terminal access is the simplest to arrange and often the most robust, while graphical access can be provided either by creating a fresh desktop session or by sharing the one already open on the Linux machine. Each approach trades ease of setup, performance and fidelity in different ways, so it helps to understand the options before settling on a configuration.
Understanding Your Requirements
The choice between methods rests primarily on three questions. First, is command-line access sufficient, or is a graphical desktop required? Second, if a desktop is needed, should it be a new session, or must it mirror the existing physical display? Third, how important is responsiveness compared to visual fidelity and feature completeness?
For administrative tasks that involve editing configuration files, managing services, or running scripts, SSH provides everything necessary. When a desktop environment is required, the decision becomes whether to view the exact state of the Linux machine's monitor or to work in a separate session.
The Three Main Options
SSH for Terminal Access
SSH requires no graphical overhead and works reliably over any connection. For many administrative tasks, this is all that is needed. Setting up SSH access is straightforward and forms the foundation for other secure operations, including file transfer and tunnelling.
RDP for New Desktop Sessions
Remote Desktop Protocol excels at creating new sessions with clean input handling and good performance over imperfect connections. RDP with a lightweight desktop such as Xfce delivers the most responsive experience for new sessions, though it does not support compositing desktops like Cinnamon well. The protocol translates keyboard and mouse input in a way that many clients have optimised for years, making it the most forgiving route when precise input behaviour matters.
VNC for Virtual or Shared Desktops
VNC can either create new virtual desktops or share the physical display. TigerVNC is suitable when a new Cinnamon session is acceptable and continuity with the local environment is valued. It can launch a full Cinnamon desktop in a virtual session, though it may feel less responsive than RDP, particularly when network conditions are suboptimal.
x11vnc mirrors the physical display exactly, making it ideal for monitoring ongoing work or providing remote guidance. This is the only option when the requirement is to see precisely what appears on the Linux machine's screen. However, it shares the same performance characteristics as other VNC solutions and is limited to showing what is already displayed locally.
Making the Choice
The decision ultimately comes down to the specific use case. If the goal is to work efficiently in a fresh desktop session with optimal responsiveness, RDP to an Xfce desktop environment is the clear choice. If maintaining the full Cinnamon experience in a new session is important, TigerVNC provides that continuity. When the task requires seeing or controlling the exact desktop session that is already running on the Linux machine, x11vnc is the only viable option.
In the articles that follow, we will examine the practical setup and configuration of x11vnc for sharing physical desktops, followed by detailed guidance on SSH, RDP and TigerVNC for those preferring terminal access or fresh desktop sessions.
What's Next
Part 2 explores x11vnc in detail, covering everything from basic setup to advanced performance tuning, input handling with KVM switches, clipboard troubleshooting and running x11vnc as a system service.
Part 3 examines SSH for terminal access, RDP with Xfce for responsive remote sessions, and TigerVNC for virtual Cinnamon desktops, along with file transfer options and operational considerations.
Some PowerShell fundamentals for practical automation
In the last few months, I have taken to using PowerShell for automating tasks while working on a new contract. There has been an element of vibe programming some of the scripts, which is why I wished to collate a reference guide that anyone can have to hand. While working with PowerShell every day does help to reinforce the learning, it also helps to look up granular concepts on a more bite-sized level. This especially matters given PowerShell's object-oriented approach. After all, many of us build things up iteratively from little steps, which also allows for more flexibility. Using an AI is all very well, yet the fastest recall is always from your on head.
1. Variables and Basic Data Types
Variables start with a dollar sign and hold values you intend to reuse, so names like $date, $outDir and $finalDir become anchors for later operations. Dates are a frequent companion in filenames and logs, and PowerShell's Get-Date makes this straightforward. A format string such as Get-Date -Format "yyyy-MM-dd" yields values like 2025-10-27, while Get-Date -Format "yyyy-MM-dd HH:mm:ss" adds a precise timestamp that helps when tracing the order of events. Because these commands return text when a format is specified, you can stitch the results into other strings without fuss.
2. File System Operations
As soon as you start handling files, you will meet a cluster of commands that make navigation robust rather than fragile. Join-Path assembles folder segments without worrying about stray slashes, Test-Path checks for the existence of a target, and New-Item creates folders when needed. Moving items with Move-Item keeps the momentum going once the structure exists.
Environment variables give cross-machine resilience; reading $env:TEMP finds the system's temporary area, and [Environment]::GetFolderPath("MyDocuments") retrieves a well-known Windows location without hard-coding. Setting context helps too, so Set-Location acts much like cd to make a directory the default focus for subsequent file operations. You can combine these approaches, as in cd ([Environment]::GetFolderPath("MyDocuments")), which navigates directly to the My Documents folder without hard-coded paths.
Scripts are often paired with nearby files, and Split-Path $ScriptPath -Parent extracts a parent folder from a full path so you can create companions in the same place. Network locations behave like local ones, with Universal Naming Convention paths beginning \ supported throughout, and Windows paths do not require careful case matching because the file systems are generally case-insensitive, which differs from many Unix-based systems. Even simple details matter, so constructing strings such as "$Folder*" is enough for wildcard searches, with backslashes treated correctly and whitespace handled sensibly.
3. Arrays and Collections
Arrays are created with @() and make it easy to keep related items together, whether those are folders in a $locs array or filenames gathered into $progs1, $progs2 and others. Indexing retrieves specific positions with square brackets, so $locs[0] returns the first entry, and a variable index like $outFiles[$i] supports loop counters.
A single value can still sit in an array using syntax such as @("bm_rc_report.sas"), which keeps your code consistent when functions always expect sequences. Any collection advertises how many items it holds using the Count property, so checking $files.Count equals zero tells you whether there is anything to process.
4. Hash Tables
When you need fast lookups, a hash table works as a dictionary that associates keys and values. Creating one with @{$locs[0] = $progs1} ties the first location to its corresponding programme list and then $locsProgs[$loc] retrieves the associated filenames for whichever folder you are considering. This is a neat stepping stone to loops and conditionals because it organises data around meaningful relationships rather than leaving you to juggle parallel arrays.
5. Control Flow
Control flow is where scripts begin to feel purposeful. A foreach loop steps through the items in a collection and is comfortable with nested passes, so you might iterate through folders, then the files inside each folder, and then a set of search patterns for those files. A for loop offers a counting pattern with initialisation, a condition and an increment written as for ($i = 0; $i -lt 5; $i++). It differs from foreach by focusing on the numeric progression rather than the items themselves.
Counters are introduced with $i = 0 and advanced with $i++, which in turn blends well with array indexing. Conditions gate work to what needs doing. Patterns such as if (-not (Test-Path ...)) reduce needless operations by creating folders only when they do not exist, and an else branch can note alternative outcomes, such as a message that a search pattern was not found.
Sometimes there is nothing to gain from proceeding, and break exits the current loop immediately, which is an efficient way to stop retrying once a log write succeeds. At other times it is better to skip just the current iteration, and continue moves directly to the next pass, which proves useful when a file list turns out to be empty for a given pattern.
6. String Operations
Strings support much of this work, so several operations are worth learning well. String interpolation allows variables to be embedded inside text using "$variable" or by wrapping expressions as "$($expression)", which becomes handy when constructing paths like "psoutput$($date)".
Splitting text is as simple as -split, and a statement such as $stub, $type = $File -split "." divides a filename around its dot, assigning the parts to two variables in one step. This demonstrates multiple variable assignment, where the first part goes to $stub and the second to $type, allowing you to decompose strings efficiently.
When transforming text, the -Replace operator substitutes all occurrences of one pattern with another, and you can chain replacements, as in -replace $Match, $Replace -replace $Match2, $Replace2, so each change applies to the modified output of the previous one.
Building new names clearly is easier with braces, as in "${stub}_${date}.txt", which prevents ambiguity when variable names abut other characters. Escaping characters is sometimes needed, so using "." treats a dot as a literal in a split operation. The backtick character ` serves as PowerShell's escape character and introduces special sequences like a newline written as `n, a tab as `t and a carriage return as `r. When you need to preserve formatting across lines without worrying about escapes, here-strings created with @" ... "@ keep indentation and line breaks intact.
7. Pipeline Operations
PowerShell's pipeline threads operations together so that the output of one command flows to the next. The pipe character | links each stage, and commands such as ForEach-Object (which processes each item), Where-Object (which filters items based on conditions) and Sort-Object -Unique (which removes duplicates) become building blocks that shape data progressively.
Within these blocks, the current item appears as $_, and properties exposed by commands can be read with syntax like $_.InputObject or $_.SideIndicator, the latter being especially relevant when handling comparison results. With pipeline formatting, you can emit compact summaries, as in ForEach-Object { "$($_.SideIndicator) $($_.InputObject)" }, which brings together multiple properties into a single line of output.
A multi-stage pipeline filtering approach often follows three stages: Select-String finds matches, ForEach-Object extracts only the values you need, and Where-Object discards anything that fails your criteria. This progressive refinement lets you start broad and narrow results step by step. There is no compulsion to over-engineer, though; a simplified pipeline might omit filtering stages if the initial search is already precise enough to return only what you need.
8. Comparison and Matching
Behind many of these steps sit comparison and matching operators that extend beyond simple equality. Pattern matching appears through -notmatch, which uses regular expressions to decide whether a value does not fit a given pattern, and it sits alongside -eq, -ne and -lt for equality, inequality and numeric comparison.
Complex conditions chain with -and, so an expression such as $_ -notmatch '^%macro$' -and $_ -notmatch '^%mend$' ensures both constraints are satisfied before an item passes through. Negative matching in particular helps exclude unwanted lines while leaving the rest untouched.
9. Regular Expressions
Regular expressions define patterns that match or search for text, often surfacing through operators such as -match and -replace. Simple patterns like .log$ identify strings ending with .log, while more elaborate ones capture groups using parentheses, as in (sdtm.|adam.), which finds two alternative prefixes.
Anchors matter, so ^ pins a match to the start of a line and $ pins it to the end, which is why ^%macro$ means an entire line consists of nothing but %macro. Character classes provide shortcuts such as w for word characters (letters, digits or underscores) and s for whitespace. The pattern "GRCw*" matches "GRC" followed by zero or more word characters, demonstrating how * controls repetition. Other quantifiers like + (one or more) and ? (zero or one) offer further control.
Escaping special characters with a backslash turns them into literals, so . matches a dot rather than any character. More complex patterns like '%(m|v).*?(?=[,(;s])' combine alternation with non-greedy matching and lookaheads to define precise search criteria.
When working with matches in pipelines, $_.Matches.Value extracts the actual text that matched the pattern, rather than returning the entire line where the match was found. This proves essential when you need just the matching portion for further processing. The syntax can appear dense at first, but PowerShell's integration means you can test patterns quickly within a pipeline or conditional, refining as you go.
10. File Content Operations
Searching file content with Select-String applies regular expressions to lines and returns match objects, while Out-File writes text to files with options such as -Append and -Encoding UTF8 to control how content is persisted.
11. File and Directory Searching
Commands for locating files typically combine path operations with filters. Get-ChildItem retrieves items from a folder, and parameters like -Filter or -Include narrow results by pattern. Wildcards such as * are often enough, but regular expressions provide finer control when integrated with pipeline operations. Recursion through subdirectories is available with -Recurse, and combining these techniques allows you to find specific files scattered across a directory tree. Once items are located, properties like FullName, Name and LastWriteTime let you decide what to do next.
12. Object Properties
Objects exposed by commands carry properties that you can access directly. $File.FullName retrieves an absolute path from a file object, while names, sizes and modification timestamps are all available as well. Subexpressions introduced with $() evaluate an inner expression within a larger string or command, which is why $($File.FullName) is often seen when embedding property values in strings. However, subexpressions are not always required; direct property access works cleanly in many contexts. For instance, $File.FullName -Replace ... reads naturally and works as you would expect because the property access is unambiguous when used as a command argument rather than embedded within a string.
13. Output and Logging
Producing output that can be read later is easier if you apply a few conventions. Write-Output sends structured lines to the console or pipeline, while Write-Warning signals notable conditions without halting execution, a helpful way to flag missing files. There are times when command output is unnecessary, and piping to Out-Null discards it quietly, for example when creating directories. Larger scripts benefit from consistency and a short custom function such as Write-Log establishes a uniform format for messages, optionally pairing console output with a line written to a file.
14. Functions
Functions tie these pieces together as reusable blocks with a clear interface. Defining one with function Get-UniquePatternMatches { } sets the structure, and a param() block declares the inputs. Strongly typed parameters like [string[]] make it clear that a function accepts an array of strings, and naming parameters $Folder and $Pattern describes their roles without additional comments.
Functions are called using named parameters in the format Get-UniquePatternMatches -Folder $loc -Pattern '(sdtm.|adam.)', which makes the intent explicit. It is common to pass several arrays into similar functions, so a function might have many parameters of the same type. Using clear, descriptive names such as $Match, $Replace, $Match2 and $Replace2 leaves little doubt about intent, even if an array of replacement rules would sometimes reduce repetition.
Positional parameters are also available; when calling Do-Compare you can omit parameter names and rely on the order defined in param(). PowerShell follows verb-noun naming conventions for functions, with common verbs including Get, Set, New, Remove, Copy, Move and Test. Following this pattern, as in Multiply-Files, places your code in the mainstream of PowerShell conventions.
It is worth avoiding a common pitfall where a function declares param([string[]]$Files) but inadvertently reads a variable like $progs from outside the function. PowerShell allows this via scope inheritance, where functions can access variables from parent scopes, but it makes maintenance harder and disguises the function's true dependencies. Being explicit about parameters creates more maintainable code.
Simple functions can still do useful work without complexity. A minimal function implementation with basic looping and conditional logic can accomplish useful tasks, and a recurring structure can be reused with minor revisions, swapping one regular expression for another while leaving the looping and logging intact. Replacement chains are flexible; add as many -replace steps as are needed, and no more.
Parameters can be reused meaningfully too, demonstrating parameter reuse where a $Match variable serves double duty: first as a filename filter in -Include, then as a text pattern for -replace. Nested function calls tie output and input together, as when piping a here-string to Out-File (Join-Path ...) to construct a file path at the moment of writing.
15. Comments
Comments play a quiet but essential role. A line starting with # explains why something is the way it is or temporarily disables a line without deleting it, which is invaluable when testing and refining.
16. File Comparison
Comparison across datasets rounds out common tasks, and Compare-Object identifies differences between two sets, telling you which items are unique to each side or shared by both. Side indicators in the output are compact: <= shows the first set, => the second, and == indicates an item present in both.
17. Common Parameters
Across many commands, common parameters behave consistently. -Force allows operations that would otherwise be blocked and overwrites existing items without prompting in contexts that support it, -LiteralPath treats a path exactly as written without interpreting wildcards, and -Append adds content to existing files rather than overwriting them. These options smooth edges when you know what you want a command to do and prefer to avoid interactive questions or unintended pattern expansion.
18. Advanced Scripting Features
A number of advanced features make scripts sturdier. Automatic variables such as $MyInvocation.MyCommand.Path provide information about the running script, including its full path, which is practical for locating resources relative to the script file. Set-StrictMode -Version Latest enforces stricter rules that turn common mistakes into immediate errors, such as using an uninitialised variable or referencing a property that does not exist. Clearing the console at the outset with Clear-Host gives a clean slate for output when a script begins.
19. .NET Framework Integration
Integration with the .NET Framework extends PowerShell's reach, and here are some examples. For instance, calling [System.IO.Path]::GetFileNameWithoutExtension() extracts a base filename using a tested library method. To gain more control over file I/O, [System.IO.File]::Open() and System.IO.StreamWriter expose low-level handles that specify sharing and access modes, which can help when you need to coordinate writing without blocking other readers. File sharing options like [System.IO.FileShare]::Read allow other processes to read a log file while the script writes to it, reducing contention and surprises.
20. Error Handling
Error handling deserves a clear pattern. Wrapping risky operations in try { } catch { } blocks captures exceptions, so a script can respond gracefully, perhaps by writing a warning and moving on. A finally block can be added for clean-up operations that must run regardless of success or failure.
When transient conditions are expected, a retry logic pattern is often enough, pairing a counter with Start-Sleep to attempt an operation several times before giving up. Waiting for a brief period such as Start-Sleep -Milliseconds 200 gives other processes time to release locks or for temporary conditions to clear.
Alongside this, checking for null values keeps assumptions in check, so conditions like if ($null -ne $process) ensure that you only read properties when an object was created successfully. This defensive approach prevents cascading errors when operations fail to return expected objects.
21. External Process Management
Managing external programmes is a common requirement, and PowerShell's Start-Process offers a controlled route. Several parameters control its behaviour precisely:
The -Wait parameter makes PowerShell pause until the external process completes, essential for sequential processing where later steps depend on earlier ones. The -PassThru parameter returns a process object, allowing you to inspect properties like exit codes after execution completes. The -NoNewWindow parameter runs the external process in the current console rather than opening a new window, keeping output consolidated. If a command expects the Command Prompt environment, calling it via cmd.exe /c $cmd integrates cleanly, ensuring compatibility with programmes designed for the CMD shell.
Exit codes reported with $process.ExitCode indicate success with zero and errors with non-zero values in most tools, so checking these numbers preserves confidence in the sequence of steps. The script demonstrates synchronous execution, processing files one at a time rather than in parallel, which can be an advantage when dependencies exist between stages or when you need to ensure ordered completion.
22. Script Termination
Scripts need to finish in a way that other tools understand. Exiting with Exit 0 signals success to schedulers and orchestrators that depend on numeric codes, while non-zero values indicate error conditions that trigger alerts or retries.
Bringing It All Together
Because this is a granular selection, it leaves it to us to piece everything together to accomplish the tasks that we have to complete. In that way, we can embed the knowledge so that we are vibe coding all the time, ensuring that a more deterministic path is followed.
Generating Git commit messages automatically using aicommit and OpenAI
One of the overheads of using version control systems like Subversion or Git is the need to create descriptive messages for each revision. Now that GitHub has its copilot, it now generates those messages for you. However, that still leaves anyone with a local git repository out in the cold, even if you are uploading to GitHub as your remote repo.
One thing that a Homebrew update does is to highlight other packages that are available, which is how I got to learn of a tool that helps with this, aicommit. Installing is just a simple command away:
brew install aicommit
Once that is complete, you now have a tool that generates messages describing very commit using GPT. For it to work, you do need to get yourself set up with OpenAI's API services and generate a token that you can use. That needs an environment variable to be set to make it available. On Linux (and Mac), this works:
export OPENAI_API_KEY=<Enter the API token here, without the brackets>
Because I use this API for Python scripting, that part was already in place. Thus, I could proceed to the next stage: inserting it into my workflow. For the sake of added automation, this uses shell scripting on my machines. The basis sequence is this:
git add .
git commit -m "<a default message>"
git push
The first line above stages everything while the second commits the files with an associated message (git makes this mandatory, much like Subversion) and the third pushes the files into the GitHub repository. Fitting in aicommit then changes the above to this:
git add .
aicommit
git push
There is now no need to define a message because aicommit does that for you, saving some effort. However, token limitations on the OpenAI side mean that the aicommit command can fail, causing the update operation to abort. Thus, it is safer to catch that situation using the following code:
git add .
if ! aicommit 2>/dev/null; then
echo " aicommit failed, using fallback"
git commit -m "<a default message>"
fi
git push
This now informs me what has happened when the AI option is overloaded and the scripts fallback to a default option that is always available with git. While there is more to my git scripting than this, the snippets included here should get across how things can work. They go well for small push operations, which is what happens most of the time; usually, I do not attempt more than that.