I'm trying to update my DSC deployment to now use partial configurations to break up the configuration. For that I need to now use a pull process instead of push.
When I try to apply the configuration for the LCM which looks something like:
[DscLocalConfigurationManager()]
Configuration CreateGESService
{
param(
[Parameter(Mandatory=$true)]
[ValidateNotNullorEmpty()]
[PsCredential] $InstallCredential,
[Parameter(Mandatory=$true)]
[ValidateNotNullorEmpty()]
[PsCredential] $RunCredential
)
Node $AllNodes.NodeName
{
$hostVersion = (get-host).Version
# changed how the possible values for debugMode in the February build
if (($hostVersion.Major -ge 5) -and ($hostVersion.Minor -ge 0) -and ($hostVersion.Build -ge 9842)){
$debugMode = 'All'
}
else{
$debugMode = $true
}
#setup the localConfigManager
Settings
{
#CertificateID = $node.Thumbprint
# slower performance - and only available WMF5
# now we need to kill the dsc
DebugMode = $debugMode
ConfigurationMode = 'ApplyAndAutoCorrect'
ConfigurationModeFrequencyMins = '15'
AllowModuleOverwrite = $true
RefreshMode = 'Push'
ConfigurationID = $node.ConfigurationID
}
PartialConfiguration GetEventStoreConfiguration {
Description = "Contains the stuff for GetEventStore Being Installed"
ConfigurationSource = "[ConfigurationRepositoryShare]ConfigSource"
RefreshMode = "Pull"
}
PartialConfiguration ExternalIntegrationConfiguration{
Description = "Contains the stuff for External Integration"
ConfigurationSource = "[ConfigurationRepositoryShare]ConfigSource"
DependsOn = '[PartialConfiguration]GetEventStoreConfiguration'
RefreshMode = "Pull"
}
PartialConfiguration ServeGroupSpike{
Description = "Contains the stuff for External Integration"
ConfigurationSource = "[ConfigurationRepositoryShare]ConfigSource"
DependsOn = '[PartialConfiguration]ExternalIntegrationConfiguration'
RefreshMode = "Pull"
}
ConfigurationRepositoryShare ConfigSource{
SourcePath = "\\someServer\Shared\dscService\Configuration"
Credential = $InstallCredential
}
ResourceRepositoryShare ResourceSource{
SourcePath = "\\someServer\Shared\dscService\Resources"
Credential = $InstallCredential
}
}
If I try to include the CertificateID I get an error like:
The property CertificateID of metaconfiguration is not compatible with the current version 2.0.0 of the configuration
document. This property only works with version greater than or equal to 1.0.0 . In case the version is greater, then
the property MinimumCompatibleVersion should be set to atleast 1.0.0 . Set these properties in the
OMI_ConfigurationDocument instance in the document and try again.
+ CategoryInfo : InvalidArgument: (root/Microsoft/...gurationManager:String) [], CimException
+ FullyQualifiedErrorId : MI RESULT 4
+ PSComputerName : SGSpike-Main
Naturally when the Configuration is attempted to be applied it can't decrypt the credentials passed, and I get an error in the event view like:
Job {B37D5239-EDBA-11E4-80C2-00155D9ACA1F} :
WarningMessage An error occured while applying the partial configuration [PartialConfiguration]ExternalIntegrationConfiguration. The error message is :
The Local Configuration Manager is not configured with a certificate. Resource '[File]GpgProgram' in configuration 'ExternalIntegrationConfiguration' cannot be processed..
Any ideas how to do this? I had this working with the certificateID when I was using a single configuration in a push model.
Even in the April 2015 drop the problem still seems to exist. Further diagnosis shows that you can:
Not use partial configurations
Not use a certificate to encrypt credentials
Opened an issue on connect (with some more details) at https://connect.microsoft.com/PowerShell/Feedback/Details/1292678
Related
I have created a ps1 file with the DSC configuration and one for the LCM.
The MOF files were created successfully.
When I start the configuration, I get the following error:
The PowerShell DSC resource MSFT_TeamsMeetingConfiguration from module
<Microsoft365DSC,1.21.728.1> does not exist at the PowerShell module
path nor is it registered as a WMI DSC resource.
The Config file looks as follows:
Configuration TeamsConfig2
{
param
(
[Parameter(Mandatory = $true,Position=0)][ValidateNotNull()][System.Management.Automation.PSCredential]$AdminCred
)
Import-DSCResource -ModuleName "Microsoft365DSC"
Node localhost
{
TeamsMeetingConfiguration DemoMeetingConfiguration2
{
ClientAppSharingPort = 50040;
ClientAppSharingPortRange = 20;
ClientAudioPort = 50000;
ClientAudioPortRange = 20;
ClientMediaPortRangeEnabled = $True;
ClientVideoPort = 50020;
ClientVideoPortRange = 20;
CustomFooterText = "sdfs"
DisableAnonymousJoin = $False;
EnableQoS = $False;
GlobalAdminAccount = $AdminCred;
HelpURL = "https://github.com/Microsoft/Office365DSC/Help";
Identity = "Global";
LegalURL = "https://github.com/Microsoft/Office365DSC/Legal";
LogoURL = "https://github.com/Microsoft/Office365DSC/Logo.png";
}
}
}
The Microsoft365DSC module is installed with version 1.21.728.1.
Get-DSCResource also finds the resource so I currently don't understand what the issue could be.
Unfortunately I can't find any examples on the internet for my scenario.
I got a DSC server with a SMB share. I want to deploy partial configs like in https://learn.microsoft.com/de-de/powershell/dsc/pull-server/partialconfigs
But there are only examples for a HTTP DSC servers not SMB. Is this also possible with an SMB DSC server? If so, could I have an example?
I have found an example:
[DSCLocalConfigurationManager()]
configuration PartialConfig
{
Node localhost
{
Settings
{
RefreshMode = 'Pull'
ConfigurationID = 'a5f86baf-f17f-4778-8944-9cc99ec9f992'
RebootNodeIfNeeded = $true
}
ConfigurationRepositoryShare SMBPull
{
SourcePath = '\\Server\Configurations'
Name = 'SMBPull'
}
PartialConfiguration OSConfig
{
Description = 'Configuration for the Base OS'
ConfigurationSource = '[ConfigurationRepositoryShare]SMBPull'
RefreshMode = 'Pull'
}
PartialConfiguration SQLConfig
{
Description = 'Configuration for the SQL Server'
DependsOn = '[PartialConfiguration]OSConfig'
RefreshMode = 'Push'
}
}
}
For the past few days, I have been trying to create a development/test environment where I can automate deployments with DSC.
I have been using WMF5.1.
The pullserver has been set up using the example: Sample_xDscWebServiceRegistrationWithSecurityBestPractices
From xPSDesiredStateConfiguration 5.1.0.0.
configuration Sample_xDscWebServiceRegistrationWithSecurityBestPractices
{
param
(
[string[]]$NodeName = 'CORE-O-DSCPull.CORE.local',
[ValidateNotNullOrEmpty()]
[string] $certificateThumbPrint,
[Parameter(HelpMessage='This should be a string with enough entropy (randomness) to protect the registration of clients to the pull server. We will use new GUID by default.')]
[ValidateNotNullOrEmpty()]
[string] $RegistrationKey # A guid that clients use to initiate conversation with pull server
)
Import-DSCResource -ModuleName xPSDesiredStateConfiguration -ModuleVersion '5.1.0.0'
Node $NodeName
{
WindowsFeature DSCServiceFeature
{
Ensure = "Present"
Name = "DSC-Service"
}
xDscWebService PSDSCPullServer
{
Ensure = "Present"
EndpointName = "PSDSCPullServer"
Port = 8080
PhysicalPath = "$env:SystemDrive\inetpub\wwwroot\PSDSCPullServer"
CertificateThumbPrint = $certificateThumbPrint
ModulePath = "$env:PROGRAMFILES\WindowsPowerShell\DscService\Modules"
ConfigurationPath = "$env:PROGRAMFILES\WindowsPowerShell\DscService\Configuration"
State = "Started"
DependsOn = "[WindowsFeature]DSCServiceFeature"
RegistrationKeyPath = "$env:PROGRAMFILES\WindowsPowerShell\DscService"
AcceptSelfSignedCertificates = $true
UseSecurityBestPractices = $true
}
File RegistrationKeyFile
{
Ensure = 'Present'
Type = 'File'
DestinationPath = "$env:ProgramFiles\WindowsPowerShell\DscService\RegistrationKeys.txt"
Contents = $RegistrationKey
}
}
}
I apply the MOF file to my pull server without issues. I create a meta MOF using the same example:
[DSCLocalConfigurationManager()]
configuration Sample_MetaConfigurationToRegisterWithSecurePullServer
{
param
(
[ValidateNotNullOrEmpty()]
[string] $NodeName = 'CORE-O-DSCPull.CORE.local',
[ValidateNotNullOrEmpty()]
[string] $RegistrationKey, #same as the one used to setup pull server in previous configuration
[ValidateNotNullOrEmpty()]
[string] $ServerName = 'CORE-O-DSCPull.CORE.local' #node name of the pull server, same as $NodeName used in previous configuration
)
Node $NodeName
{
Settings
{
RefreshMode = 'Pull'
}
ConfigurationRepositoryWeb CORE-O_PullSrv
{
ServerURL = "https://$ServerName`:8080/PSDSCPullServer.svc" # notice it is https
RegistrationKey = $RegistrationKey
ConfigurationNames = #('Basic')
}
}
}
I apply the LCM settings to my pull-server without a problem.
I can create a simple basic.mof and use DSC to apply it. All this works fine.
Next, I create another meta.mof file for another node to let it register to my pull-server. I use the same configuration as above except for the nodename, which I change to the name of the other node. I use the command:
Set-DscLocalConfigurationManager -ComputerName <nodename> -path <pathtonewmetamof>
This command works correctly. That machine can then use DSC to apply the same basic.mof without problems.
Here comes the problem:
I restart my pull server and node, create a new basic.mof and try to apply this to both my machines. This procedure works fine on the pull server itself, but my node can no longer apply the basic.mof, because it will no longer register with my pull-server. I have replicated this many times, where I would install both machines from scratch and configure them. Every time I restart my machines, registration stops working. See the error below:
Registration of the Dsc Agent with the server https://CORE-O-DSCPull.CORE.local:8080/PSDSCPullServer.svc failed. The underlying error is: Failed to register Dsc
Agent with AgentId 1FE837AA-C774-11E6-80B5-9830B2A0FAC0 with the server
https://core-o-dscpull.core.local:8080/PSDSCPullServer.svc/Nodes(AgentId='1FE837AA-C774-11E6- 80B5-9830B2A0FAC0'). .
+ CategoryInfo : InvalidResult: (root/Microsoft/...gurationManager:String) [], CimException
+ FullyQualifiedErrorId : RegisterDscAgentCommandFailed,Microsoft.PowerShell.DesiredStateConfiguration.Commands.RegisterDscAgentCommand
+ PSComputerName : CORE-O-DC.CORE.local
So, my problem is that registration seems to work fine until I reboot the pull server. Does anyone have any idea what can cause this issue?
For those wondering if I managed to fix this, yes I did.
It appears to be a bug in WMF5.0 and I was only using WMF5.1 on the pullserver. Not on the node. So I had to update that and now it is working.
As explained in this blog entry the low-level problem is that WMF 5.0 uses TLS 1.0 to communicate with the server, while WFM 5.1 does no longer support TLS 1.0.
In the aforementioned entry you will find two solutions: one that implies upgrading WMF in every and each of the nodes, and another that allows less secure connections by modifying the register in the server.
I have a powershell module which attempts to upload a blob to azure storage. Everything checks out until the last line which actually uploads the blob.
I receive the following error:
Exception calling "UploadText" with "1" argument(s):
"The specified resource does not exist."
At line:1 char:1
+ $blob.UploadText("asdasdfsdf")
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : StorageClientException
I have also tried using the overload with 3 args, but the same issue exists there as well.
Here is the module:
Function Add-BlobText
{
[CmdletBinding()]
param(
[Parameter(Mandatory = $true,Position = 0)]
[string]
$StorageAccount,
[Parameter(Mandatory = $true,Position = 1)]
[string]
$Container,
[Parameter(Mandatory = $true,Position = 2)]
[string]
$BlobName,
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string]
$BlobText
) #end param
Add-Type -Path "C:\Assemblies\Microsoft.WindowsAzure.StorageClient.dll"
Set-AzureSubscription -SubscriptionName "MySubName"
$secondaryKey = (Get-AzureStorageKey -StorageAccountName $storageAccount).Secondary
$creds = New-Object Microsoft.WindowsAzure.StorageCredentialsAccountAndKey($StorageAccount,$secondaryKey)
$cloudStorageAccount = New-Object Microsoft.WindowsAzure.CloudStorageAccount($creds, $true)
[Microsoft.WindowsAzure.StorageClient.CloudBlobClient]$cloudBlobClient = New-Object Microsoft.WindowsAzure.StorageClient.CloudBlobClient($cloudStorageAccount.BlobEndpoint)
[Microsoft.WindowsAzure.StorageClient.CloudBlobContainer]$blobContainer = $cloudBlobClient.GetContainerReference($Container)
[Microsoft.WindowsAzure.StorageClient.CloudBlob]$blob = $blobContainer.GetBlobReference($BlobName)
$blob.UploadText($BlobText)
} #end Function Add-BlobText
Update:
I have been able to get this working as a binary module (below). If anyone can figure out why UploadText() works within a binary module but throws an exception in a script module, please let me know.
[Cmdlet(VerbsCommon.Add, "BlobText")]
public class AddBlobText : PSCmdlet
{
[Parameter(Mandatory = true, Position = 0)]
public string StorageAccount { get; set; }
[Parameter(Mandatory = true, Position = 1)]
public string Container { get; set; }
[Parameter(Mandatory = true, Position = 2)]
public string BlobName { get; set; }
[Parameter(Mandatory = true, ValueFromPipeline = true)]
public string BlobText { get; set; }
protected override void ProcessRecord()
{
PowerShell ps = PowerShell.Create();
ps.AddScript("Set-AzureSubscription -SubscriptionName 'MySubName'");
string keyScript = "( Get-AzureStorageKey -StorageAccountName " + StorageAccount + " ).Secondary";
ps.AddScript(keyScript);
Collection<PSObject> result = ps.Invoke();
string secondaryKey = result[0].ToString();
StorageCredentialsAccountAndKey credentials = new StorageCredentialsAccountAndKey(StorageAccount, secondaryKey);
CloudStorageAccount storageAccount = new CloudStorageAccount(credentials, true);
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference(Container);
var blob = container.GetBlobReference(BlobName);
blob.UploadText(BlobText);
}
}
This is probably because your container does not exist. You should call CreateIfNotExist after initializing the container to make sure it exists:
[Microsoft.WindowsAzure.StorageClient.CloudBlobContainer]$blobContainer = $cloudBlobClient.GetContainerReference($Container)
$blobContainer.CreateIfNotExist() <-- Here
[Microsoft.WindowsAzure.StorageClient.CloudBlob]$blob = $blobContainer.GetBlobReference($BlobName)
$blob.UploadText($BlobText)
This error is very ambiguous and misleading but there are instances' where Azure Storage can get "confused". Looking at Sandrino's example and specifically this line,
[Microsoft.WindowsAzure.StorageClient.CloudBlob]$blob = $blobContainer.GetBlobReference($BlobName)
Not that Sandrino's answer is your issue but the exception you encountered will happen when passing a Url or possibly other confusing key strings to Azure Storage Containers.
Unfortunately I am not a Powershell guy but here is a reproducing example then fix in C#.
public void Save(string objId, T obj)
{
CloudBlob blob = this.container.GetBlobReference(objId); // Problematic if a URL
blob.Properties.ContentType = "application/json";
var serialized = string.Empty;
serialized = serializer.Serialize(obj);
if (this.jsonpSupport)
{
serialized = this.container.Name + "Callback(" + serialized + ")";
}
blob.UploadText(serialized);
}
Assume that this.container is a valid blob storage instance pointing to http://127.0.0.1:10000/devstoreaccount1/sggames or whatever you have for a valid container.
And objId is a key that contains a Url like https://www.google.com/accounts/o8/id?id=AItOawk4Dw9sLxSc-zmdWQHdZNcyzkTcvKUkhiE ...and yes this can happen, in my case this is an actual identity claim from Google using Azure ACS.
After the GetBlobReference call the blob instance has become corrupt which now looks at a messed up Uri -> https://www.google.com/accounts/o8/id?id=AItOawk4Dw9sLxSc-zmdWQHdZNcyzkTcvKUkhiE
Unfortunately the solution to simply call $blobContainer.CreateIfNotExist() is not a solution and wouldn't work. Key's that contain a Uri structure will simply be used to re-interpret the blob storage location.
The work around (other than daredev's Update) would be something like this:
if (Uri.IsWellFormedUriString(claim, UriKind.Absolute) && HttpUtility.ParseQueryString(claim).Count > 0)
{
claim = HttpUtility.ParseQueryString(claim)[0];
}
Add this code within my method above to clean up any Uri's, but you could use any appropriate method like Base64 encoding URLs if you need to maintain the full key.
Here are the before and after images showing the results as I described.
The bad:
notice the bad URI
this bad URI munged up the actual storage blob location
here is the same exception daredev had
The good:
the new scrubbed key, notice it's just the value on the URL's query string
Azure Storage URI looks good now
Eureka!
Hope this helps.
This is the PowerShell script I use to upload a file to Azure Blob: Uploading to Azure Storage
$SubscriptionName = ""
$SubscriptionId = ""
$DestContainer = ""
$StorageAccountName = ""
Import-AzurePublishSettingsFile -PublishSettingsFile "<Location of the publishsettings-file>"
Set-AzureSubscription -SubscriptionId $SubscriptionId -CurrentStorageAccountName $StorageAccountName
Select-AzureSubscription -SubscriptionName $SubscriptionName
Set-AzureStorageBlobContent -File "<File you want to upload>" -Container $DestContainer
I'm interested in getting the contents of a shelveset at the command prompt. Now, you would think that a cmdlet such as Get-TfsShelveset, available in the TFS Power Tools, would do this. You might also think that "tf.exe shelvesets" would do this.
However, unless I've missed something, I'm appalled to report that neither of these is the case. Instead, each command requires you to give it a shelveset name, and then simply regurgitates a single line item for that shelveset, along with some metadata about the shelveset such as creationdate, displayname, etc. But as far as I can tell, no way to tell what's actually in the shelf.
This is especially heinous for Get-TfsShelveset, which has the ability to include an array of file descriptors along with the Shelveset object it returns. I even tried to get clever, thinking that I could harvest the file names from using -WhatIf with Restore-TfsShelveset, but sadly Restore-TfsShelveset doesn't implement -WhatIf.
Please, someone tell me I'm wrong about this!
tf status /shelveset:name
will list out the content of the named shelveset (you can also supplier an owner: see tf help status).
With the TFS PowerToy's PowerShell snapin:
Get-TfsPendingChange -Shelveset name
for the same information.
It is possible to construct a small command-line application that uses the TFS SDK, which returns the list of files contained in a given shelveset.
The sample below assumes knowledge of the Shelveset name & it's owner:
using System;
using System.IO;
using System.Collections.ObjectModel;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Common;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
namespace ShelvesetDetails
{
class Program
{
static void Main(string[] args)
{
Uri tfsUri = (args.Length < 1) ? new Uri("TFS_URI") : new Uri(args[0]);
TfsConfigurationServer configurationServer = TfsConfigurationServerFactory.GetConfigurationServer(tfsUri);
ReadOnlyCollection<CatalogNode> collectionNodes = configurationServer.CatalogNode.QueryChildren(
new[] { CatalogResourceTypes.ProjectCollection },
false, CatalogQueryOptions.None);
CatalogNode collectionNode = collectionNodes[0];
Guid collectionId = new Guid(collectionNode.Resource.Properties["InstanceId"]);
TfsTeamProjectCollection teamProjectCollection = configurationServer.GetTeamProjectCollection(collectionId);
var vcServer = teamProjectCollection.GetService<VersionControlServer>();
Shelveset[] shelves = vcServer.QueryShelvesets(
"SHELVESET_NAME", "SHELVESET_OWNER");
Shelveset shelveset = shelves[0];
PendingSet[] sets = vcServer.QueryShelvedChanges(shelveset);
foreach (PendingSet set in sets)
{
PendingChange[] changes = set.PendingChanges;
foreach (PendingChange change in changes)
{
Console.WriteLine(change.FileName);
}
}
}
}
}
Invoking this console app & catching the outcome during execution of the powershell should be possible.
Try:
tfpt review
/shelveset:shelvesetName;userName
You may also need to add on the server option so something like:
tfpt review /shelveset:Code Review;jim
/sever:company-source
I think this is what you are looking for.
This is what I ended up with, based on pentelif's code and the technique in the article at http://akutz.wordpress.com/2010/11/03/get-msi/ linked in my comment.
function Get-TfsShelvesetItems
{
[CmdletBinding()]
param
(
[string] $ShelvesetName = $(throw "-ShelvesetName must be specified."),
[string] $ShelvesetOwner = "$env:USERDOMAIN\$env:USERNAME",
[string] $ServerUri = $(throw "-ServerUri must be specified."),
[string] $Collection = $(throw "-Collection must be specified.")
)
$getShelvesetItemsClassDefinition = #'
public IEnumerable<PendingChange> GetShelvesetItems(string shelvesetName, string shelvesetOwner, string tfsUriString, string tfsCollectionName)
{
Uri tfsUri = new Uri(tfsUriString);
TfsConfigurationServer configurationServer = TfsConfigurationServerFactory.GetConfigurationServer(tfsUri);
ReadOnlyCollection<CatalogNode> collectionNodes = configurationServer.CatalogNode.QueryChildren( new[] { CatalogResourceTypes.ProjectCollection }, false, CatalogQueryOptions.None);
CatalogNode collectionNode = collectionNodes.Where(node => node.Resource.DisplayName == tfsCollectionName).SingleOrDefault();
Guid collectionId = new Guid(collectionNode.Resource.Properties["InstanceId"]);
TfsTeamProjectCollection teamProjectCollection = configurationServer.GetTeamProjectCollection(collectionId);
var vcServer = teamProjectCollection.GetService<VersionControlServer>();
var changes = new List<PendingChange>();
foreach (Shelveset shelveset in vcServer.QueryShelvesets(shelvesetName, shelvesetOwner))
{
foreach (PendingSet set in vcServer.QueryShelvedChanges(shelveset))
{
foreach ( PendingChange change in set.PendingChanges )
{
changes.Add(change);
}
}
}
return changes.Count == 0 ? null : changes;
}
'#;
$getShelvesetItemsType = Add-Type `
-MemberDefinition $getShelvesetItemsClassDefinition `
-Name "ShelvesetItemsAPI" `
-Namespace "PowerShellTfs" `
-Language CSharpVersion3 `
-UsingNamespace System.IO, `
System.Linq, `
System.Collections.ObjectModel, `
System.Collections.Generic, `
Microsoft.TeamFoundation.Client, `
Microsoft.TeamFoundation.Framework.Client, `
Microsoft.TeamFoundation.Framework.Common, `
Microsoft.TeamFoundation.VersionControl.Client `
-ReferencedAssemblies "C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\ReferenceAssemblies\v2.0\Microsoft.TeamFoundation.Client.dll", `
"C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\ReferenceAssemblies\v2.0\Microsoft.TeamFoundation.Common.dll", `
"C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\ReferenceAssemblies\v2.0\Microsoft.TeamFoundation.VersionControl.Client.dll" `
-PassThru;
# Initialize an instance of the class.
$getShelvesetItems = New-Object -TypeName "PowerShellTfs.ShelvesetItemsAPI";
# Emit the pending changes to the pipeline.
$getShelvesetItems.GetShelvesetItems($ShelvesetName, $ShelvesetOwner, $ServerUri, $Collection);
}
Spent a few days trying to do this as well, this always popped up on google so here is what I found to help future generations:
To get the contents of the shelveset (at least with Team Explorer Everywhere),
use the command: tf difference /shelveset:<Shelveset name>
That will print out the contents of the shelveset and give filenames in the form :
<Changetype>: <server file path>; C<base change number>
Shelved Change: <server file path again>;<shelveset name>
So if your file is contents/test.txt
in the shelveset shelve1 (with base revision 1), you will see :
edit: $/contents/file.txt;C1
Shelved Change: $/contents/file.txt;shelve1
After that, using the tf print command
(or view if not using TEE) on $/contents/file.txt;shelve1 should get you the contents :
tf print $/contents/file.txt;shelve1
Shows you what is in the file.txt in shelveset shelve1
If you want get shelveset changes from server by using tfs command
Using power shell:
Get-TfsPendingChange -Server http://example.com/org -Shelveset shelvsetName
Using vs commands:
c:\projects>tf shelvesets BuddyTest_23
more info about this please see here
https://learn.microsoft.com/en-us/azure/devops/repos/tfvc/shelvesets-command?view=azure-devops