Versioning #
LSUClient uses a three-part version number MAJOR.MINOR.PATCH and follows SemVer 2.0.0.
This means you can generally expect all your scripts and integrations to keep working with any one major version (such as 1.x.x).
There is only ever one current release of LSUClient, there is no parallel maintenance of older releases or prior versions. As soon as a new release is out the prior one is obsoleted.
What is and isn’t covered by the semantic versioning promise #
Semantic versioning communicates changes in a softwares “public API”.
For the purpose of a PowerShell Module, the public API is the exported functions (cmdlets), classes and the returned objects.
Explicitly exempt from this public API are:
- Private functions
- Internal classes
- Hidden properties
- Output streams other than the success stream (1)
The data structure, type, name, content and behavior of these may change at any point without notice as they are only intended for internal use by LSUClient itself, or in the case of non-success output streams such as Verbose and Debug, only for logging and “human consumption” and not to support scripting workloads.
Minor breaking changes in minor versions #
I must admit, I sometimes make exceptions from this rule for breaking changes I consider to be “very very minor” in that:
- I feel they are likely not going to impact any or only very few users
- They are trivial to adjust for, as in the change(s) required to get everything working like before with the new version are very small
Any such “minor breaking change” will be noted in the release notes, and will also be accompanied by a bump of the MINOR version number.
List of breaking changes #
Version 1.3.0 - changed the type of the URL
property on the [LenovoUpdate]
objects
#
To support the custom package repositories that were introduced in this version, I’ve changed the type of the URL
property
on the LenovoUpdate
objects that Get-LSUpdate
returns from [System.Uri]
to [System.String]
.
If you were accessing a property or method unique to the [System.Uri]
object, for example:
# Pretend-script written for LSUClient 1.2.5
$OneUpdate = Get-LSUpdate -All | Select-Object -First 1
if ($OneUpdate.URL.Host -like "*.com") {
Write-Output "Hey! This update was sourced from a .com domain!"
} else {
Write-Output "Some other domain!"
}
that would have broken because once $OneUpdate.URL
became a string, it no longer had a Host
property.
This means in the code snippet above $OneUpdate.URL.Host
will evaluate to $null
and either always print Some other domain!
or error with The property 'Host' cannot be found on this object.
if you are running in PowerShell Strict Mode.
However, a quick fix to get the same snippet working again could be to just cast $OneUpdate.URL
back to [System.Uri]
:
# Pretend-script updated for LSUClient 1.3.0+
$OneUpdate = Get-LSUpdate -All | Select-Object -First 1
if (([Uri]$OneUpdate.URL).Host -like "*.com") {
Write-Output "Hey! This update was sourced from a .com domain!"
} else {
Write-Output "Some other domain!"
}
Version 1.8.0 - changed how file paths in custom repositories are resolved #
This change only affects those who use LSUClient with a self-hosted repository and access it via a filesystem path (so not served via a webserver / HTTP(S)). This version changes how LSUClient finds files, that is updates (package XMLs) and their components (installers etc.) in your repository. To understand what exactly is different and whether this affects your repository, let me explain the previous behavior first (LSUClient <=1.7.1):
- First, LSUClient checks whether the path to a file, exactly as it is specified inside your repositories’ XML files, exists.
- If a file exists at that path, LSUClient will use that file.
- If a file does NOT exist at that path, LSUClient will attempt to prepend the path with either:
- The path to your repository if the path to be resolved is to a package definition XML file
- The path to the package (containing directory of the package XML definition) if the path to be resolved is to a packages’ component, such as an installer and then check whether a file exists at the joined path location.
- If a file exists at that path, LSUClient will use that file.
This was a simple and logical process that offered great flexibility to custom repository/package maintainers. You can put any path you want, to anywhere, into your XML files and LSUClient will go get that file if possible. You could spread a repository over multiple locations such as network shares, USB thumbdrives, webservers etc. at the same time just by putting the files there and making sure every path in your XML definitions points to the right place. You could also reference files by relative paths and those would be resolved from a sensible base directory. Unless…
Relative paths on Windows can be relative to different locations. Most commonly, to the current directory:
.\file.ext
./file.ext
file.ext
But also to the current directory of a specific drive:
C:file.ext
D:file.ext
Or to the root of the current drive:
\file.ext
/file.ext
See the official docs for the most comprehensive overview:
https://learn.microsoft.com/dotnet/standard/io/file-path-formats
The latter forms of paths may be used less frequently, but they are just as valid and can be useful.
LSUClient handles all kinds of paths correctly, because it just uses Test-Path
and Get-Item
internally which, unsurprisingly, support all of Windows’ path forms.
Unfortunately though Lenovos Update Retriever tool, which is one way to create and manage internal package repositories, creates an XML file which references all of the package XML definitions in the repository like this:
<Package ...>
...
<LocalPath>\r0zav10w\r0zav10w_2_.xml</LocalPath>
</Package>
Notice how this is a totally valid, relative path that points to the r0zav10w_2_.xml
file within the r0zav10w
directory in the root of the current drive. But that’s not actually what Lenovo means here, these LocalPath
s are meant to be relative to the root directory of your repository. They should be using .\r0zav10w\r0zav10w_2_.xml
or even just r0zav10w\r0zav10w_2_.xml
then, but they don’t and so LSUClient 1.3.0 to 1.7.1 will dutifully first look for C:\r0zav10w\r0zav10w_2_.xml
(assuming C: is the current drive) and only concatenate the LocalPath with the repository root path if that file doesn’t exist, which it usually won’t.
However because standard users by default have permission to create directories inside drive roots such as C:\ and to then create files therein, an unelevated user could intentionally create this directory structure for LSUClient to find and run programs of the users choosing as if they were package installers or external detection programs. If you regularly run LSUClient elevated through a scheduled task, an RMM or software deployment tool etc. this is a potential avenue for persistent privilege escalation for a malintentioned, knowledgeable user.
While I maintain that LSUClient interpreted the above path correctly, I also recognize the popularity of Update Retriever and so I made the decision to implement a fix on my end. To eliminate surprising behavior when pointing LSUClient to a repository created with Update Retriever, LSUClient 1.8.0 now treats relative paths differently:
- First, LSUClient checks whether the path to a file, exactly as it is specified inside your repositories’ XML files, is fully-qualified or relative
- If it is fully-qualified and a file exists at that path, LSUClient will use that file.
- If it is not fully-qualified (aka is relative) OR it was fully-qualified but the file could not be found, LSUClient will attempt to prepend the path with either:
- The path to your repository if the path to be resolved is to a package definition XML file
- The path to the package (containing directory of the package XML definition) if the path to be resolved is to a packages’ component, such as an installer and then check whether a file exists at the joined path location.
- If a file exists at that path, LSUClient will use that file.
The key difference is that LSUClient 1.8.0+ no longer resolves relative paths according to Windows’ standards. Instead, they are always prepended with a known, trusted base directory. And this applies to all kinds of relative paths, not just directory-relative. Even those that are relative to the root of the current drive, like Update Retriever uses them, are now (technically wrongfully) interpreted as relative to the repository or package directory.
Thus you can no longer intentionally use file paths in your database.xml, model XML or package XML definitions that are relative to:
- The root of the current drive (e.g.
\dir\file.ext
) - The current directory of a specific drive (e.g.
C:file.ext
) - The current working directory of PowerShell
Use only absolute, fully-qualified paths or paths relative to the repository or individual packages’ root directory. This is more limiting than the previous behavior, but I hope it still satisfies 99.9% of usecases. If this regression affects you, please open an issue detailing your usecase, I will try to accomodate it if safely possible. This change was made in response to issue 122.