Skip to content

Commit e90df5f

Browse files
authored
Merge pull request #1148 from Gijsreyn/gh-57/main/add-base64string-function
Add `base64ToString()` function
2 parents c51be62 + ba9bfec commit e90df5f

File tree

6 files changed

+377
-1
lines changed

6 files changed

+377
-1
lines changed
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
---
2+
description: Reference for the 'base64ToString' DSC configuration document function
3+
ms.date: 09/30/2025
4+
ms.topic: reference
5+
title: base64ToString
6+
---
7+
8+
# base64ToString
9+
10+
## Synopsis
11+
12+
Converts a base64 representation to a string.
13+
14+
## Syntax
15+
16+
```Syntax
17+
base64ToString(<base64Value>)
18+
```
19+
20+
## Description
21+
22+
The `base64ToString()` function converts a [base64][01] encoded string back to
23+
its original string representation. This function is the inverse of the
24+
[`base64()`][02] function and is useful for decoding base64-encoded
25+
configuration data, secrets, or content that was previously encoded for safe
26+
transmission or storage.## Examples
27+
28+
### Example 1 - Decode a base64 string
29+
30+
The configuration decodes a base64-encoded string back to its original value.
31+
32+
```yaml
33+
# base64ToString.example.1.dsc.config.yaml
34+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
35+
resources:
36+
- name: Decode base64 string
37+
type: Microsoft.DSC.Debug/Echo
38+
properties:
39+
output: "[base64ToString('aGVsbG8gd29ybGQ=')]"
40+
```
41+
42+
```bash
43+
dsc config get --file base64ToString.example.1.dsc.config.yaml
44+
```
45+
46+
```yaml
47+
results:
48+
- name: Decode base64 string
49+
type: Microsoft.DSC.Debug/Echo
50+
result:
51+
actualState:
52+
output: hello world
53+
messages: []
54+
hadErrors: false
55+
```
56+
57+
### Example 2 - Round-trip encoding and decoding
58+
59+
The configuration demonstrates encoding a string to base64 and then decoding it
60+
back using the [`base64()`][02] function inside the `base64ToString()` function.
61+
62+
```yaml
63+
# base64ToString.example.2.dsc.config.yaml
64+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
65+
resources:
66+
- name: Round-trip base64 conversion
67+
type: Microsoft.DSC.Debug/Echo
68+
properties:
69+
output: "[base64ToString(base64('Configuration Data'))]"
70+
```
71+
72+
```bash
73+
dsc config get --file base64ToString.example.2.dsc.config.yaml
74+
```
75+
76+
```yaml
77+
results:
78+
- name: Round-trip base64 conversion
79+
type: Microsoft.DSC.Debug/Echo
80+
result:
81+
actualState:
82+
output: Configuration Data
83+
messages: []
84+
hadErrors: false
85+
```
86+
87+
### Example 3 - Decode configuration from parameters
88+
89+
This example shows decoding base64-encoded configuration data passed through
90+
parameters, which is common when passing complex data through deployment
91+
systems that require base64 encoding.
92+
93+
```yaml
94+
# base64ToString.example.3.dsc.config.yaml
95+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
96+
parameters:
97+
encodedConfig:
98+
type: string
99+
defaultValue: eyJzZXJ2ZXJOYW1lIjoid2ViLXNlcnZlci0wMSIsInBvcnQiOjgwODB9
100+
resources:
101+
- name: Decode server configuration
102+
type: Microsoft.DSC.Debug/Echo
103+
properties:
104+
output: "[base64ToString(parameters('encodedConfig'))]"
105+
```
106+
107+
```bash
108+
dsc config get --file base64ToString.example.3.dsc.config.yaml
109+
```
110+
111+
```yaml
112+
results:
113+
- name: Decode server configuration
114+
type: Microsoft.DSC.Debug/Echo
115+
result:
116+
actualState:
117+
output: '{"serverName":"web-server-01","port":8080}'
118+
messages: []
119+
hadErrors: false
120+
```
121+
122+
### Example 4 - Decode with error handling
123+
124+
This example demonstrates how the function handles invalid base64 input by
125+
using the [`if()`][03] function to provide fallback behavior.
126+
127+
```yaml
128+
# base64ToString.example.4.dsc.config.yaml
129+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
130+
parameters:
131+
possiblyEncodedData:
132+
type: string
133+
defaultValue: validBase64String=
134+
fallbackData:
135+
type: string
136+
defaultValue: default configuration
137+
resources:
138+
- name: Safe decode with fallback
139+
type: Microsoft.DSC.Debug/Echo
140+
properties:
141+
output:
142+
decodedValue: "[base64ToString(parameters('possiblyEncodedData'))]"
143+
fallback: "[parameters('fallbackData')]"
144+
```
145+
146+
```bash
147+
dsc --file base64ToString.example.4.dsc.config.yaml config get
148+
```
149+
150+
```yaml
151+
results:
152+
- name: Safe decode with fallback
153+
type: Microsoft.DSC.Debug/Echo
154+
result:
155+
actualState:
156+
output:
157+
decodedValue: waEb(KidString
158+
fallback: default configuration
159+
messages: []
160+
hadErrors: false
161+
```
162+
163+
## Parameters
164+
165+
### base64Value
166+
167+
The `base64ToString()` function expects a single string containing valid
168+
base64-encoded data. The function decodes the base64 representation back to
169+
the original string. If the value isn't a valid base64 string, DSC raises an
170+
error. If the decoded bytes don't form valid UTF-8, DSC also raises an error.
171+
172+
```yaml
173+
Type: string
174+
Required: true
175+
MinimumCount: 1
176+
MaximumCount: 1
177+
```
178+
179+
## Output
180+
181+
The `base64ToString()` function returns the decoded string representation of
182+
the **base64Value** parameter.
183+
184+
```yaml
185+
Type: string
186+
```
187+
188+
## Exceptions
189+
190+
The `base64ToString()` function raises errors for the following conditions:
191+
192+
- **Invalid base64 encoding**: When the input string contains characters or
193+
patterns that are not valid base64
194+
- **Invalid UTF-8**: When the decoded bytes do not form valid UTF-8 text
195+
196+
## Related functions
197+
198+
- [`base64()`][02] - Encodes a string to base64 format
199+
- [`string()`][04] - Converts values to strings
200+
- [`parameters()`][05] - Retrieves parameter values
201+
- [`if()`][03] - Returns values based on a condition
202+
203+
<!-- Link reference definitions -->
204+
[01]: https://en.wikipedia.org/wiki/Base64
205+
[02]: ./base64.md
206+
[03]: ./if.md
207+
[04]: ./string.md
208+
[05]: ./parameters.md

docs/reference/schemas/config/functions/createArray.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ resources:
4242
```
4343
4444
```bash
45-
dsc config get --file createArray.example.1.dsc.config.yaml config get
45+
dsc config get --file createArray.example.1.dsc.config.yaml
4646
```
4747

4848
```yaml

dsc/tests/dsc_functions.tests.ps1

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,4 +737,46 @@ Describe 'tests for function expressions' {
737737
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw)
738738
$out.results[0].result.actualState.output | Should -BeExactly $expected
739739
}
740+
741+
It 'base64ToString function works for: <expression>' -TestCases @(
742+
@{ expression = "[base64ToString('aGVsbG8gd29ybGQ=')]"; expected = 'hello world' }
743+
@{ expression = "[base64ToString('')]"; expected = '' }
744+
@{ expression = "[base64ToString('aMOpbGxv')]"; expected = 'héllo' }
745+
@{ expression = "[base64ToString('eyJrZXkiOiJ2YWx1ZSJ9')]"; expected = '{"key":"value"}' }
746+
@{ expression = "[base64ToString(base64('test message'))]"; expected = 'test message' }
747+
) {
748+
param($expression, $expected)
749+
750+
$escapedExpression = $expression -replace "'", "''"
751+
$config_yaml = @"
752+
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
753+
resources:
754+
- name: Echo
755+
type: Microsoft.DSC.Debug/Echo
756+
properties:
757+
output: '$escapedExpression'
758+
"@
759+
$out = $config_yaml | dsc config get -f - | ConvertFrom-Json
760+
$out.results[0].result.actualState.output | Should -Be $expected
761+
}
762+
763+
It 'base64ToString function error handling: <expression>' -TestCases @(
764+
@{ expression = "[base64ToString('invalid!@#')]" ; expectedError = 'Invalid base64 encoding' }
765+
@{ expression = "[base64ToString('/w==')]" ; expectedError = 'Decoded bytes do not form valid UTF-8' }
766+
) {
767+
param($expression, $expectedError)
768+
769+
$config_yaml = @"
770+
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
771+
resources:
772+
- name: Echo
773+
type: Microsoft.DSC.Debug/Echo
774+
properties:
775+
output: `"$expression`"
776+
"@
777+
$null = dsc -l trace config get -i $config_yaml 2>$TestDrive/error.log
778+
$LASTEXITCODE | Should -Not -Be 0
779+
$errorContent = Get-Content $TestDrive/error.log -Raw
780+
$errorContent | Should -Match $expectedError
781+
}
740782
}

dsc_lib/locales/en-us.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,12 @@ invoked = "array function"
247247
[functions.base64]
248248
description = "Encodes a string to Base64 format"
249249

250+
[functions.base64ToString]
251+
description = "Converts a base64 representation to a string"
252+
invoked = "base64ToString function"
253+
invalidBase64 = "Invalid base64 encoding"
254+
invalidUtf8 = "Decoded bytes do not form valid UTF-8"
255+
250256
[functions.bool]
251257
description = "Converts a string or number to a boolean"
252258
invoked = "bool function"
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use base64::{Engine as _, engine::general_purpose};
5+
6+
use crate::DscError;
7+
use crate::configure::context::Context;
8+
use crate::functions::{FunctionArgKind, FunctionCategory, FunctionMetadata};
9+
use rust_i18n::t;
10+
use serde_json::Value;
11+
use super::Function;
12+
use tracing::debug;
13+
14+
#[derive(Debug, Default)]
15+
pub struct Base64ToString {}
16+
17+
impl Function for Base64ToString {
18+
fn get_metadata(&self) -> FunctionMetadata {
19+
FunctionMetadata {
20+
name: "base64ToString".to_string(),
21+
description: t!("functions.base64ToString.description").to_string(),
22+
category: vec![FunctionCategory::String],
23+
min_args: 1,
24+
max_args: 1,
25+
accepted_arg_ordered_types: vec![vec![FunctionArgKind::String]],
26+
remaining_arg_accepted_types: None,
27+
return_types: vec![FunctionArgKind::String],
28+
}
29+
}
30+
31+
fn invoke(&self, args: &[Value], _context: &Context) -> Result<Value, DscError> {
32+
debug!("{}", t!("functions.base64ToString.invoked"));
33+
34+
let base64_value = args[0].as_str().unwrap();
35+
36+
let decoded_bytes = general_purpose::STANDARD.decode(base64_value).map_err(|_| {
37+
DscError::FunctionArg(
38+
"base64ToString".to_string(),
39+
t!("functions.base64ToString.invalidBase64").to_string(),
40+
)
41+
})?;
42+
43+
let result = String::from_utf8(decoded_bytes).map_err(|_| {
44+
DscError::FunctionArg(
45+
"base64ToString".to_string(),
46+
t!("functions.base64ToString.invalidUtf8").to_string(),
47+
)
48+
})?;
49+
50+
Ok(Value::String(result))
51+
}
52+
}
53+
54+
#[cfg(test)]
55+
mod tests {
56+
use crate::configure::context::Context;
57+
use crate::parser::Statement;
58+
use serde_json::Value;
59+
60+
#[test]
61+
fn base64_to_string_simple() {
62+
let mut parser = Statement::new().unwrap();
63+
let result = parser
64+
.parse_and_execute("[base64ToString('aGVsbG8gd29ybGQ=')]", &Context::new())
65+
.unwrap();
66+
assert_eq!(result, Value::String("hello world".to_string()));
67+
}
68+
69+
#[test]
70+
fn base64_to_string_empty() {
71+
let mut parser = Statement::new().unwrap();
72+
let result = parser
73+
.parse_and_execute("[base64ToString('')]", &Context::new())
74+
.unwrap();
75+
assert_eq!(result, Value::String("".to_string()));
76+
}
77+
78+
#[test]
79+
fn base64_to_string_unicode() {
80+
let mut parser = Statement::new().unwrap();
81+
let result = parser
82+
.parse_and_execute("[base64ToString('aMOpbGxv')]", &Context::new())
83+
.unwrap();
84+
assert_eq!(result, Value::String("héllo".to_string()));
85+
}
86+
87+
#[test]
88+
fn base64_to_string_round_trip() {
89+
let mut parser = Statement::new().unwrap();
90+
let result = parser
91+
.parse_and_execute("[base64ToString(base64('test message'))]", &Context::new())
92+
.unwrap();
93+
assert_eq!(result, Value::String("test message".to_string()));
94+
}
95+
96+
#[test]
97+
fn base64_to_string_invalid_base64() {
98+
let mut parser = Statement::new().unwrap();
99+
let result = parser.parse_and_execute("[base64ToString('invalid!@#')]", &Context::new());
100+
assert!(result.is_err());
101+
}
102+
103+
#[test]
104+
fn base64_to_string_invalid_utf8() {
105+
let mut parser = Statement::new().unwrap();
106+
let result = parser.parse_and_execute("[base64ToString('/w==')]", &Context::new());
107+
assert!(result.is_err());
108+
}
109+
110+
#[test]
111+
fn base64_to_string_json_string() {
112+
let mut parser = Statement::new().unwrap();
113+
let result = parser
114+
.parse_and_execute("[base64ToString('eyJrZXkiOiJ2YWx1ZSJ9')]", &Context::new())
115+
.unwrap();
116+
assert_eq!(result, Value::String("{\"key\":\"value\"}".to_string()));
117+
}
118+
}

0 commit comments

Comments
 (0)