Skip to content

Commit 242d451

Browse files
Add event: Swiss PSUG 09/2026 (#77)
Co-authored-by: Gilbert Sanchez <me@gilbertsanchez.com>
1 parent 7c622bc commit 242d451

9 files changed

Lines changed: 164 additions & 3 deletions

File tree

.github/workflows/build.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,7 @@ jobs:
9696
--gc \
9797
--minify \
9898
--destination public
99+
100+
- name: Validate iCalendar feed
101+
run: node scripts/validate-calendar.mjs public/calendar/calendar.ics
102+

assets/css/fontawesome-subset.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,6 @@
7474
.fa-twitter:before{content:"\f099"}
7575
.fa-user-circle:before{content:"\f2bd"}
7676
.fa-users:before{content:"\f0c0"}
77+
.fa-video:before{content:"\f03d"}
7778
.fa-windows:before{content:"\f17a"}
7879
.fa-youtube:before{content:"\f167"}

assets/css/tailwind.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

assets/fonts/fa-solid-subset.woff2

80 Bytes
Binary file not shown.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
endDate: '2026-09-09'
3+
externalUrl: https://www.meetup.com/swiss-powershell-user-group/events/315958850/
4+
startDate: '2026-09-09'
5+
title: Swiss PSUG 09/2026
6+
virtual: true
7+
where: Bern, Switzerland
8+
---
9+
**Place & Language:**
10+
Place: Isolutions AG, Schanzenstrasse 4c, 3008 Bern (Hybrid with Teams)
11+
Language: EN
12+
13+
**Session 1: More Than Scripting: Advanced PowerShell with C#**
14+
Description:
15+
Have you ever wished PowerShell had generics, better performance, or easier access to low-level .NET features? It already does—you just need a little C#. This session explores how experienced PowerShell users can leverage C# without becoming full-time C# developers. We’ll cover practical integration techniques, discuss common pitfalls, and demonstrate how projects like PowerShell itself, the Microsoft Graph SDK, and many community modules combine both languages to deliver powerful tooling.
16+
Speaker: [MVP Fabien Tschanz](https://www.linkedin.com/in/fabientschanz/)
17+
18+
**Session 2: The Azure VM Extension Mystery**
19+
Description:
20+
Have you ever checked an Azure VM extension with PowerShell and thought you knew which version was installed? Think again.
21+
In this session, we'll dive into the surprisingly tricky world of Azure VM Extensions and discover why the information returned by standard PowerShell cmdlets is not always enough. Using a real-world example, we'll investigate how Azure exposes extension versions, why build numbers can be difficult to obtain, and how browser developer tools can reveal the hidden REST API calls used by the Azure Portal.
22+
If you enjoy PowerShell, reverse engineering portal behaviour, and solving cloud mysteries, this session is for you.
23+
Speaker: [MVP Andres Bohren](https://www.linkedin.com/in/andres-bohren/)
24+
25+
**Agenda:**
26+
17:00 Welcome
27+
17:30 Session 1
28+
18:15 Session 2
29+
19:00 Networking and Beer
30+
20:00 End of Event

hugo.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ params:
146146
branch: "main"
147147

148148
ical:
149-
timezone: UTC
149+
timezone: Etc/UTC
150150

151151
module:
152152
imports:

layouts/list.calendar.ics

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{{- $calendar := "BEGIN:VCALENDAR\n" -}}
2+
{{- $calendar = printf "%s%s" $calendar (partial "header.ics" .) -}}
3+
{{- $timezones := slice -}}
4+
{{- range .Pages -}}
5+
{{- if .Params.startDate -}}
6+
{{- $timezone := partial "ical/get_timezone.ics" . -}}
7+
{{- if not (in $timezones $timezone) -}}
8+
{{- $timezones = $timezones | append $timezone -}}
9+
{{- end -}}
10+
{{- end -}}
11+
{{- end -}}
12+
{{- range $timezones -}}
13+
{{- $calendar = printf "%s%s" $calendar (partial "timezone.ics" .) -}}
14+
{{- end -}}
15+
{{- range .Pages -}}
16+
{{- if .Params.startDate -}}
17+
{{- $calendar = printf "%s%s" $calendar (partial "event.ics" .) -}}
18+
{{- end -}}
19+
{{- end -}}
20+
{{- $calendar = printf "%sEND:VCALENDAR\n" $calendar -}}
21+
{{- replace $calendar "\n" "\r\n" -}}

scripts/validate-calendar.mjs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { readFile } from 'node:fs/promises';
2+
3+
const input = process.argv[2];
4+
5+
if (!input) {
6+
throw new Error('Usage: node scripts/validate-calendar.mjs <calendar.ics|URL>');
7+
}
8+
9+
const calendar = new URL(input, 'file:').protocol === 'file:'
10+
? await readFile(input, 'utf8')
11+
: await (await fetch(input)).text();
12+
13+
const errors = [];
14+
const fail = (message) => errors.push(message);
15+
16+
if (/(^|[^\r])\n/.test(calendar)) {
17+
fail('Content lines must use CRLF line endings.');
18+
}
19+
20+
const lines = calendar.replace(/\r?\n[ \t]/g, '').split(/\r?\n/).filter(Boolean);
21+
const components = [];
22+
const events = [];
23+
const timezones = new Set();
24+
const timezoneReferences = new Set();
25+
let event;
26+
27+
for (const line of lines) {
28+
if (line.startsWith('BEGIN:')) {
29+
const component = line.slice('BEGIN:'.length);
30+
components.push(component);
31+
if (component === 'VEVENT') {
32+
event = new Map();
33+
events.push(event);
34+
}
35+
continue;
36+
}
37+
38+
if (line.startsWith('END:')) {
39+
const component = line.slice('END:'.length);
40+
if (components.pop() !== component) {
41+
fail(`Mismatched component terminator: ${line}`);
42+
}
43+
if (component === 'VEVENT') {
44+
event = undefined;
45+
}
46+
continue;
47+
}
48+
49+
const separator = line.indexOf(':');
50+
if (separator < 1) {
51+
fail(`Malformed content line: ${line}`);
52+
continue;
53+
}
54+
55+
const declaration = line.slice(0, separator);
56+
const [name, ...parameters] = declaration.split(';');
57+
const value = line.slice(separator + 1);
58+
59+
for (const parameter of parameters) {
60+
if (parameter.startsWith('TZID=')) {
61+
timezoneReferences.add(parameter.slice('TZID='.length));
62+
}
63+
}
64+
65+
if (components.at(-1) === 'VTIMEZONE' && name === 'TZID') {
66+
timezones.add(value);
67+
}
68+
69+
if (event) {
70+
event.set(name, [...(event.get(name) ?? []), value]);
71+
}
72+
}
73+
74+
if (components.length) {
75+
fail(`Unclosed component: ${components.at(-1)}`);
76+
}
77+
78+
if (lines[0] !== 'BEGIN:VCALENDAR' || lines.at(-1) !== 'END:VCALENDAR') {
79+
fail('Calendar must be wrapped in BEGIN:VCALENDAR and END:VCALENDAR.');
80+
}
81+
82+
for (const [index, properties] of events.entries()) {
83+
for (const property of ['UID', 'DTSTAMP', 'DTSTART']) {
84+
if (properties.get(property)?.length !== 1) {
85+
fail(`VEVENT ${index + 1} must contain exactly one ${property}.`);
86+
}
87+
}
88+
89+
const uid = properties.get('UID')?.[0];
90+
if (uid && events.some((other) => other !== properties && other.get('UID')?.[0] === uid)) {
91+
fail(`VEVENT ${index + 1} reuses UID ${uid}.`);
92+
}
93+
}
94+
95+
for (const timezone of timezoneReferences) {
96+
if (!timezones.has(timezone)) {
97+
fail(`TZID=${timezone} has no matching VTIMEZONE component.`);
98+
}
99+
}
100+
101+
if (errors.length) {
102+
throw new Error(`Invalid RFC 5545 calendar:\n- ${errors.join('\n- ')}`);
103+
}
104+
105+
console.log(`Validated ${events.length} VEVENT components.`);

themes/powershell-community/layouts/_default/calendar.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ <h2 class="text-3xl font-bold text-gray-900">Upcoming Events</h2>
6565
<!-- Event Details -->
6666
<div class="p-6 flex-1">
6767
<h3 class="text-xl font-bold text-gray-900 mb-2">{{ .Title }}</h3>
68-
<p class="text-gray-600 mb-3">{{ .Summary }}</p>
68+
<p class="text-gray-600 mb-3">{{ .Summary | plainify | truncate 240 }}</p>
6969
<div class="flex flex-wrap items-center gap-4 text-sm text-gray-500">
7070
<div class="flex items-center">
7171
<i class="fas fa-map-marker-alt text-purple-600 mr-2"></i>

0 commit comments

Comments
 (0)