Add PsTypeName post #14
31
.drone.yml
31
.drone.yml
@ -53,11 +53,25 @@ steps:
|
||||
repo: ocram85/blog
|
||||
tags: "next"
|
||||
dockerfile: Dockerfile
|
||||
build_args:
|
||||
- NODE_BASE=lts-buster-slim
|
||||
- NGINX_BASE=1.21.6-alpine
|
||||
|
||||
- name: "Trivy (next)"
|
||||
image: aquasec/trivy
|
||||
failure: ignore
|
||||
commands:
|
||||
- "trivy image --exit-code 1 --no-progress ocram85/blog:next"
|
||||
- |
|
||||
trivy image \
|
||||
--severity UNKNOWN,LOW,MEDIUM \
|
||||
--no-progress \
|
||||
ocram85/blog:next
|
||||
- |
|
||||
trivy image \
|
||||
--exit-code 1 \
|
||||
--severity HIGH,CRITICAL \
|
||||
--no-progress \
|
||||
ocram85/blog:next
|
||||
|
||||
- name: "Trigger Service Update"
|
||||
image: ocram85/portainer-serviceupdate
|
||||
@ -100,11 +114,24 @@ steps:
|
||||
repo: ocram85/blog
|
||||
auto_tag: true
|
||||
dockerfile: Dockerfile
|
||||
build_args:
|
||||
- NODE_BASE=lts-buster-slim
|
||||
- NGINX_BASE=1.21.6-alpine
|
||||
|
||||
- name: "Trivy (latest)"
|
||||
image: aquasec/trivy
|
||||
commands:
|
||||
- "trivy image --exit-code 1 --no-progress ocram85/blog:latest"
|
||||
- |
|
||||
trivy image \
|
||||
--severity UNKNOWN,LOW,MEDIUM \
|
||||
--no-progress \
|
||||
ocram85/blog:latest
|
||||
- |
|
||||
trivy image \
|
||||
--exit-code 1 \
|
||||
--severity HIGH,CRITICAL \
|
||||
--no-progress \
|
||||
ocram85/blog:latest
|
||||
|
||||
- name: "Trigger Service Update"
|
||||
image: ocram85/portainer-serviceupdate
|
||||
|
@ -1,4 +1,8 @@
|
||||
FROM node:lts-buster-slim as builder
|
||||
# Build ARGS for base image versions
|
||||
ARG NODE_BASE=lts-buster-slim
|
||||
ARG NGINX_BASE=1.21.6-alpine
|
||||
|
||||
FROM node:${NODE_BASE} as builder
|
||||
COPY . /src
|
||||
#RUN ls -la
|
||||
WORKDIR /src
|
||||
@ -6,7 +10,7 @@ WORKDIR /src
|
||||
RUN npm install \
|
||||
&& npm run build
|
||||
|
||||
FROM nginx:1.21.5-alpine
|
||||
FROM nginx:${NGINX_BASE} as prod
|
||||
LABEL maintainer="marco.blessing@googlemail.com"
|
||||
HEALTHCHECK --interval=15s --timeout=5s \
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost:80/ || exit 1
|
||||
|
@ -3,6 +3,9 @@ title: "{{ replace .Name "-" " " | title }}"
|
||||
date: {{ .Date }}
|
||||
draft: true
|
||||
|
||||
categories: ['']
|
||||
tags: ['']
|
||||
|
||||
# lastmod: {{ .Date}}
|
||||
# showDateUpdated: true
|
||||
|
||||
|
@ -47,6 +47,7 @@ dateFormat = "2 January 2006"
|
||||
# { slack = "https://workspace.url/team/userid" },
|
||||
# { snapchat = "https://snapchat.com/add/username" },
|
||||
# { soundcloud = "https://soundcloud.com/username" },
|
||||
{ stack-overflow = "https://stackoverflow.com/users/5222635/ocram85" },
|
||||
# { steam = "https://steamcommunity.com/profiles/userid" },
|
||||
# { telegram = "https://t.me/username" },
|
||||
# { tiktok = "https://tiktok.com/@username" },
|
||||
|
238
content/posts/pstypename/index.md
Normal file
238
content/posts/pstypename/index.md
Normal file
@ -0,0 +1,238 @@
|
||||
---
|
||||
title: 'Parameter Validation with PSTypeName'
|
||||
date: 2022-03-16T09:24:56+01:00
|
||||
#draft: true
|
||||
|
||||
categories: ['PowerShell']
|
||||
tags: ['parameter', 'validation', 'PSTypeName']
|
||||
# lastmod: 2022-03-16T09:24:56+01:00
|
||||
# showDateUpdated: true
|
||||
|
||||
# custom overrides for pages
|
||||
# showDate: false
|
||||
# showAuthor: false
|
||||
# showWordCount: false
|
||||
# showReadingTime: false
|
||||
# showTableOfContents: false
|
||||
# showTaxonomies: true
|
||||
# showEdit: false
|
||||
# sharingLinks: [null]
|
||||
---
|
||||
|
||||
![ship](ship.jpg 'Photo by [Rod Long](https://unsplash.com/@rodlong) on [Unsplash](https://unsplash.com)')
|
||||
|
||||
## 🖼️ Intro
|
||||
|
||||
This post explains how to use `PSCustomObject`s as function parameters. We compare the basic usage with an
|
||||
advanced one using the `[PSTypeName()]` parameter attribute.
|
||||
|
||||
## 🗑️ Well-Known Workflow
|
||||
|
||||
So let's start with a common object definition how it is used with a function:
|
||||
|
||||
```powershell
|
||||
$Rocinante = [PSCustomObject]@{
|
||||
Owner = 'Martian Congressional Republic Navy'
|
||||
Type = 'Light Frigate'
|
||||
Class = 'Corvette'
|
||||
Registry = 'ECF-270'
|
||||
HullNumber = '158'
|
||||
LengthInMeter = 46
|
||||
Name = 'Rocinante'
|
||||
}
|
||||
```
|
||||
|
||||
As you can see, a `PSCustomObject` has still the the same class type and just differs by its note properties.
|
||||
|
||||
```bash
|
||||
> $Rocinante | Get-Member
|
||||
|
||||
TypeName: System.Management.Automation.PSCustomObject
|
||||
|
||||
Name MemberType Definition
|
||||
---- ---------- ----------
|
||||
Equals Method bool Equals(System.Object obj)
|
||||
GetHashCode Method int GetHashCode()
|
||||
GetType Method type GetType()
|
||||
ToString Method string ToString()
|
||||
Class NoteProperty string Class=Corvette
|
||||
HullNumber NoteProperty string HullNumber=158
|
||||
LengthInMeter NoteProperty int LengthInMeter=46
|
||||
Name NoteProperty string Name=Rocinante
|
||||
Owner NoteProperty string Owner=Martian Congressional Republic Navy
|
||||
Registry NoteProperty string Registry=ECF-270
|
||||
Type NoteProperty string Type=Light Frigate
|
||||
|
||||
> $Rocinante.PSObject.TypeNames
|
||||
System.Management.Automation.PSCustomObject
|
||||
System.Object
|
||||
```
|
||||
|
||||
So we can use the out object as an function parameter.
|
||||
|
||||
```powershell
|
||||
function Invoke-Launch {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[PSCustomObject]$Ship
|
||||
)
|
||||
|
||||
begin {}
|
||||
|
||||
process {
|
||||
# Manual input validation for $Ship
|
||||
# test if all needed properties are present.
|
||||
|
||||
$DockLength = 50
|
||||
if ($Ship.LengthInMeter -gt $DockLength) {
|
||||
Write-Error -Message "Ship doesn't fit in the docking station." -ErrorAction 'Stop'
|
||||
}
|
||||
# ...
|
||||
# ...
|
||||
}
|
||||
|
||||
end {}
|
||||
}
|
||||
```
|
||||
|
||||
This common pattern could fail whenever someone changes your object properties. If the _LengthInMeter_ property is missing you ran into an error. E.g.:
|
||||
|
||||
```console
|
||||
> $Rocinante = [PSCustomObject]@{ foo = 'bar' }
|
||||
> Invoke-Launch -Ship $Rocinante
|
||||
```
|
||||
|
||||
{{< note >}}
|
||||
Keep in mind - Because we are using here custom objects and not class instances, we can not use `Rocinante` as a parameter type like `[Rocinante]$Ship` which would solve this immediately.
|
||||
{{< /note >}}
|
||||
|
||||
To fix this we can use the `[PSTypeName()]` parameter attribute, to ensure an object with the correct type name is used. This doesn't verify your parameters but minimize the risk for using invalid parameter objects.
|
||||
|
||||
## 🛡️ PSTypeName Usage
|
||||
|
||||
Let's first modify the object creation and use a custom type name.
|
||||
|
||||
```powershell
|
||||
$Rocinante = [PSCustomObject]@{
|
||||
# You can use special property 'PSTypeName'
|
||||
# to set it implicit within the creation.
|
||||
PSTypeName = 'Ship.Corvette.LightFrigate'
|
||||
Owner = 'Martian Congressional Republic Navy'
|
||||
Type = 'Light Frigate'
|
||||
Class = 'Corvette'
|
||||
Registry = 'ECF-270'
|
||||
HullNumber = '158'
|
||||
LengthInMeter = 46
|
||||
Name = 'Rocinante'
|
||||
}
|
||||
# Legacy syntax for injection a custom type name
|
||||
# $Rocinante.PSObject.TypeNames.insert(0,'Ship.Corvette.LightFrigate')
|
||||
```
|
||||
|
||||
```bash
|
||||
> $Rocinante | Get-Member
|
||||
|
||||
TypeName: Ship.Corvette.LightFrigate
|
||||
|
||||
Name MemberType Definition
|
||||
---- ---------- ----------
|
||||
Equals Method bool Equals(System.Object obj)
|
||||
GetHashCode Method int GetHashCode()
|
||||
GetType Method type GetType()
|
||||
ToString Method string ToString()
|
||||
Class NoteProperty string Class=Corvette
|
||||
HullNumber NoteProperty string HullNumber=158
|
||||
LengthInMeter NoteProperty int LengthInMeter=46
|
||||
Name NoteProperty string Name=Rocinante
|
||||
Owner NoteProperty string Owner=Martian Congressional Republic Navy
|
||||
Registry NoteProperty string Registry=ECF-270
|
||||
Type NoteProperty string Type=Light Frigate
|
||||
|
||||
> $Rocinante.PSObject.TypeNames
|
||||
Ship.Corvette.LightFrigate
|
||||
System.Management.Automation.PSCustomObject
|
||||
System.Object
|
||||
```
|
||||
|
||||
Now we can replace the `[PSCustomObject]` parameter type by `[PSTypeName()]`
|
||||
|
||||
```powershell
|
||||
function Invoke-Launch {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[PSTypeName('Ship.Corvette.LightFrigate')]$Ship
|
||||
)
|
||||
|
||||
begin {}
|
||||
|
||||
process {
|
||||
$DockLength = 50
|
||||
if ($Ship.LengthInMeter -gt $DockLength) {
|
||||
Write-Error -Message "Ship doesn't fit in the docking station." -ErrorAction 'Stop'
|
||||
}
|
||||
# ...
|
||||
# ...
|
||||
}
|
||||
|
||||
end {}
|
||||
}
|
||||
```
|
||||
|
||||
## 💭 Final Thoughts
|
||||
|
||||
Over time, your PowerShell functions become more and more complex. You will reach a point where you start using
|
||||
objects as parameters. This is where the PSTypeName parameter attribute shown can help you.
|
||||
|
||||
In my experience, the ability to create custom classes _(introduced in PowerShell 5)_ is rarely used for this.
|
||||
|
||||
Most PowerShell users I know have a SysOp or DevOps background. Few come from software development and try to use
|
||||
OOP paradigms and patterns.
|
||||
|
||||
Therefore I would also avoid using complex classes, especially if they use not only properties but also methods.
|
||||
|
||||
Like already mentioned `PSTypeName` just tests the used type name and not your definition details.
|
||||
You should consider creating a your objects within a wrapper function to mimic a class constructor:
|
||||
|
||||
```powershell
|
||||
function New-LightFrigate {
|
||||
[CmdletBinding()]
|
||||
[OutputType('Ship.Corvette.LightFrigate')]
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$Registry,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$HullNumber,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$Name
|
||||
)
|
||||
|
||||
begin {}
|
||||
|
||||
process {
|
||||
$Ship = [PSCustomObject]@{
|
||||
PSTypeName = 'Ship.Corvette.LightFrigate'
|
||||
Owner = 'Martian Congressional Republic Navy'
|
||||
Type = 'Light Frigate'
|
||||
Class = 'Corvette'
|
||||
Registry = $Registry
|
||||
HullNumber = $HullNumber
|
||||
LengthInMeter = 46
|
||||
Name = $Name
|
||||
}
|
||||
Write-Output $Ship
|
||||
}
|
||||
|
||||
end {}
|
||||
}
|
||||
|
||||
$Rocinante = New-LightFrigate -Name 'Rocinante' -Registry 'DE-MB2' -HullNumber '158'
|
||||
```
|
BIN
content/posts/pstypename/ship.jpg
Normal file
BIN
content/posts/pstypename/ship.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 78 KiB |
@ -1 +1 @@
|
||||
Subproject commit 480492a976a577d5ee563a69b57c7b45b519a23c
|
||||
Subproject commit 56fc286336c136cbc5cc094769721b4393dd7863
|
Loading…
Reference in New Issue
Block a user