Auto Login SAP GUI with powershell script - powershell

I'm trying to write a script that automatically logs into an SAP system via SAP GUI. I want the SAP GUI fields to be filled automatically with the script below.
Can you tell me, if I'm on the right way? How can I let it work?
#-Begin-----------------------------------------------------------------
#-Includes------------------------------------------------------------
."$PSScriptRoot\COM.ps1"
$hWSH = Create-Object "Wscript.Shell"
$hWSH.Popup("testmessage", 2, "goto", 1)
Free-Object $hWSH
#-Signatures----------------------------------------------------------
$Sig = #'
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
'#
# FindWindow function---------------------------------------------
$Win32 = Add-Type -Namespace Win32 -Name Funcs -MemberDefinition $Sig -PassThru
#-Set the path to the SAP GUI directory-------------------------------
$SAPGUIPath = "C:\Program Files (x86)\SAP\FrontEnd\SAPgui\"
# SAP system ID-----------------------------------------------
$SID = "test.lan"
#instance number of the SAP system---------------------------
$InstanceNo = "10"
#-Start SAP GUI---------------------------------------------------
$SAPGUI = $SAPGUIPath + "sapgui.exe"
& $SAPGUI $SID $InstanceNo
#-Wait until the session is available---------------------------------
While ($Win32::FindWindow("SAP_FRONTEND_SESSION", "SAP") -eq 0) {
Start-Sleep -Milliseconds 250
}
#-Logon to SAP --------------------------------------------
$user="test"
$SAPGUI.document.getElementById("Benutzer").value= "$user"
$SAPGUI.document.getElementById("loginform").submit()
```

You could use SAP shortcut:
cd "c:\Program Files (x86)\SAP\FrontEnd\SAPgui\"
sapshcut -guiparm="[hostname] [installation number]" -system=[system id] -client=[client] -user=[user name] -pw=[password]
Replace the parameters (square brackets) with the appropriate values. You will see a confirmation popup when you execute this command for the first time (for a specific set of parameters), but you can disable the dialog for future automatic logins.
You may omit parameter -guiparm="[hostname] [installation number]" if the system ID is created in SAPlogon.

Related

How to kick off ExtendScript JSX script from Powershell

I want to be able to execute an Adobe Illustrator ExtendScript via Windows Powershell. I believe this should be possible due to this answer that describes using VB via COM.
This is my Powershell script:
$illustratorRef = New-Object -ComObject Illustrator.Application
$conversionScript = New-Object -ComObject Scripting.FileSystemObject
$scriptFile = $conversionScript.OpenTextFile("C:\ws\ArtConversion\alert-test.jsx")
$fileContents = $scriptFile.ReadAll()
$scriptFile.Close()
$fileToRun = $fileContents + "main(arguments)"
$args = "line1", "line2"
$illustratorRef.DoJavaScript($fileToRun, $args, 1)
Here is the alert-test.jsx script:
function main(argv) {
alert('message: ' + argv[0]);
return argv[0];
}
Running the Powershell script opens Illustrator, but throws the following error upon encountering $illustratorRef.DoJavaScript:
Library not registered. (Exception from HRESULT: 0x8002801D (TYPE_E_LIBNOTREGISTERED))
I am using Adobe Illustrator 2019 CC (64bit) and Powershell 5.1.16299.666
I achieved my goal, but wasn't able to do it 100% with Powershell.
The 2017 Adobe Illustrator Scripting Guide contains this statement on page 22:
In VBScript, there are several ways to create an instance of Illustrator.
When referring to JavaScript however, it says:
Information on launching Illustrator from JavaScript is beyond the scope of this guide.
I couldn't find any official documentation on how to programmatically start Illustrator on Windows using other languages besides VB, so I ended up letting my Powershell script handle the heavy lifting of directory traversal and logging, while having it open Illustrator by means of a Visual Basic script.
The call from Powershell into VB looks like this:
$convertFile = "cmd /C cscript .\run-illustrator-conversion.vbs $arg1, $arg2"
$output = Invoke-Expression $convertFile
The VB script ended up looking like this:
Dim appRef
Dim javaScriptFile
Dim argsArr()
Dim fsObj : Set fsObj = CreateObject("Scripting.FileSystemObject")
Dim jsxFile : Set jsxFile = fsObj.OpenTextFile(".\script-to-run.jsx", 1, False)
Dim fileContents : fileContents = jsxFile.ReadAll
jsxFile.Close
Set jsxFile = Nothing
Set fsObj = Nothing
javascriptFile = fileContents & "main(arguments);"
Set appRef = CreateObject("Illustrator.Application.CS5")
ReDim argsArr(Wscript.Arguments.length - 1)
For i = 0 To Wscript.Arguments.length - 1
argsArr(i) = Wscript.Arguments(i)
Next
Wscript.Echo appRef.DoJavaScript(javascriptFile, argsArr, 1)
Note: Check scripting guide to get correct string for your version of Illustrator.

Calling AppDomain.DoCallback from Powershell

This is based on the Stack Overflow question: How to load an assembly as reflection-only in a new AppDomain?
I am attempting to determine the runtime version of an assembly, but that assembly could be loaded multiple times as I traverse through nested folders. Loading the assembly directly using
[Reflection.Assembly]::ReflectionOnlyLoadFrom($assembly)
will therefore not work, as the assembly can only be loaded once in the app-domain.
Given the following function to load an assembly in a separate AppDomain:
function Load-AssemblyInNewAppDomain($assembly)
{
Write-Host $assembly.FullName
$domain = [AppDomain]::CreateDomain([Guid]::NewGuid())
$domain.DoCallback
({
$loaded = [Reflection.Assembly]::Load($assembly)
$runtime = $loaded.ImageRuntimeVersion
Write-Host $runtime
})
}
This outputs the contents of the delegate to the console, rather than executing it:
OverloadDefinitions
-------------------
void DoCallBack(System.CrossAppDomainDelegate callBackDelegate)
void _AppDomain.DoCallBack(System.CrossAppDomainDelegate theDelegate)
$loaded = [Reflection.Assembly]::Load($assembly)
$runtime = $loaded.ImageRuntimeVersion
Write-Host $runtime
Note that the results are the same, whether I use PowerShell 4 or 5
Any help/guidance appreciated
First thought: don't muck around with AppDomains at all and use a completely separate process. Those are (relatively) easily launched from PowerShell, at least. The drawback is that it's potentially much slower if you're doing this for lots of files.
$myAssemblyPath = "C:\..."
$getImageRuntimeVersion = {
[Reflection.Assembly]::ReflectionOnlyLoadFrom($input).ImageRuntimeVersion
}
$encodedCommand = [Convert]::ToBase64String(
[Text.Encoding]::Unicode.GetBytes($getImageRuntimeVersion)
)
$imageRuntimeVersion = $myAssemblyPath | powershell -EncodedCommand $encodedCommand
So, is there no way at all to do this with AppDomains in PowerShell? Well, there is, but it's not pretty. You can't use AppDomain.DoCallBack because, as you've discovered, PowerShell can't remote delegates that way (because, under the covers, it produces dynamic methods).
However, it's easy to host the PowerShell runtime, and all PowerShell objects know how to serialize (a requirement for cross-domain remoting), so invoking a PowerShell script in another AppDomain is fairly simple (but still ugly):
$scriptInvokerAssembly = [System.IO.Path]::GetTempFileName() + ".dll"
Add-Type -OutputAssembly $tempAssembly -TypeDefinition #"
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Management.Automation;
public class ScriptInvoker : MarshalByRefObject {
public IEnumerable<PSObject> Invoke(ScriptBlock scriptBlock, PSObject[] parameters) {
using (var powerShell = PowerShell.Create()) {
powerShell.Commands.AddScript(scriptBlock.ToString());
if (parameters != null) {
powerShell.AddParameters(parameters);
}
return powerShell.Invoke();
}
}
}
"#
[Reflection.Assembly]::LoadFile($scriptInvokerAssembly) | Out-Null
Function Invoke-CommandInTemporaryAppDomain([ScriptBlock] $s, [object[]] $arguments) {
$setup = New-Object System.AppDomainSetup
$setup.ApplicationBase = Split-Path ([ScriptInvoker].Assembly.Location) -Parent
$domain = [AppDomain]::CreateDomain([Guid]::NewGuid(), $null, $setup)
$scriptInvoker = $domain.CreateInstanceAndUnwrap(
[ScriptInvoker].Assembly.FullName, [ScriptInvoker]
);
$scriptInvoker.Invoke($s, $arguments)
[AppDomain]::Unload($domain)
}
And now you can do
Invoke-CommandInTemporaryAppDomain {
[Reflection.Assembly]::ReflectionOnlyLoadFrom($args[0]).ImageRuntimeVersion
} $myAssemblyPath
Note that we have to generate a temporary assembly on disk and have AppDomain load it from there. This is ugly, but you can't have Add-Type produce an in-memory assembly, and even if you do end up with a byte[] getting that to load in another AppDomain is anything but trivial because you can't hook AppDomain.AssemblyResolve in PowerShell. If this command was packaged in a module, you'd compile the assembly containing the ScriptInvoker ahead of time, so I don't see working around this as a priority.
You can't run DoCallback via powershell alone. But DoCallBack does work with some inline C#. As Jeroen says it's ugly, but this works:
$assm = "C:\temp\so\bin\dynamic-assembly.dll"
Add-Type -TypeDefinition #"
using System.Reflection;
using System;
namespace Example
{
public class AppDomainUtil
{
public void LoadInAppDomain(AppDomain childDomain, string assemblyName)
{
childDomain.SetData("assemblyName", assemblyName);
childDomain.DoCallBack( new CrossAppDomainDelegate(LoadAssembly)) ;
}
public static void LoadAssembly()
{
string assemblyName = (string)AppDomain.CurrentDomain.GetData("assemblyName");
// console not available from another domain
string log = "c:\\temp\\hello.txt";
System.IO.File.WriteAllText(log, string.Format("Hello from {0}\r\n",AppDomain.CurrentDomain.FriendlyName));
System.IO.File.AppendAllText(log, string.Format("Assembly to load is {0}\r\n",assemblyName));
Assembly loaded = Assembly.Load(assemblyName);
System.IO.File.AppendAllText(log, string.Format("Assemblyloaded: {0}\r\n",loaded.FullName));
}
}
}
"# -OutputAssembly $assm -OutputType Library # must set output assembly otherwise assembly generated in-memory and it will break with Type errors.
Add-Type -Path $assm
function Load-AssemblyInNewAppDomain([string]$assembly) {
Write-Host "Parent domain: $([AppDomain]::CurrentDomain.FriendlyName)"
$util = New-Object Example.AppDomainUtil
$ads = New-Object System.AppDomainSetup
$cd = [AppDomain]::CurrentDomain
# set application base
$ads.ApplicationBase = [IO.path]::GetDirectoryName( $assm )
[System.AppDomain]$newDomain = [System.AppDomain]::CreateDomain([System.Guid]::NewGuid().ToString(), $null, $ads);
Write-Host "Created child domain: $($newDomain.FriendlyName)"
$util.LoadInAppDomain($newDomain, $assembly)
}
Testing it out:
PS C:\WINDOWS\system32> Load-AssemblyInNewAppDomain "".GetType().Assembly.FullName
Parent domain: PowerShell_ISE.exe
Created child domain: 61ab2dbb-8b33-4e7e-84db-5fabfded53aa
PS C:\WINDOWS\system32> cat C:\temp\hello.txt
Hello from 61ab2dbb-8b33-4e7e-84db-5fabfded53aa
Assembly to load is mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Assemblyloaded: mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

How to check if HTA is foreground window?

I'm trying to determine if an HTA is the foreground window. The following PowerShell will normally identify the foreground window:
Add-Type #"
using System;
using System.Runtime.InteropServices;
public class UserWindows {
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
}
"#
$a = [UserWindows]::GetForegroundWindow()
get-process | ? { $_.mainwindowhandle -eq $a }
But, if the HTA is in the foreground, no process is returned. (I.E. there is a MainWindowHandle, but there is no process!?)
The MSHTA process has an entirely different MainWindowHandle, but no window.
Process Hacker identifies the HTA window (frame or contents) as the mshta process.
How can I match up the HTA window and mshta.exe via script? Alternatively, how can I get the MainWindowHandle of the HTA window without knowing if it is in front?
The Foregroundwindow returned is not a mainwindowhandle of any process but just a window handle of mshta, you'll have to check all window handles.
I did manually with the tool cmdow.exe (had to convert the handle to hex) and got this for my example The HTA helpomatic :
> cmdow 0x14E0F46
Handle Lev Pid -Window status- Image Caption
0x14E0F46 1 153048 Res Ina Ena Vis mshta The HTA Helpomatic -- Presented by t
There should be better/more PowerShellish ways to enumerate the window handles but this changed script will use the mentioned cmdow.exe
## Get-ForegrounWindow.ps1
Add-Type #"
using System;
using System.Runtime.InteropServices;
public class UserWindows {
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
}
"#
while ($true -eq 'true') {
$ForeGroundWin = [UserWindows]::GetForegroundWindow()
$handle = "0x{0:x}" -f $($ForeGroundWin.ToInt64())
cmdow.exe $Handle /B /F
Sleep -sec 5
}
Sample output:
> .\Get-ForegrounWindow.ps1
0x15C04A2 1 127148 Res Act Ena Vis powershell Windows PowerShell
0x7F0DE4 1 135416 Res Act Ena Vis TextPad TextPad - Q:\Test\2017-06\09\Get-ForegrounWindow.ps1
0x16205D0 1 121732 Res Act Ena Vis bash usernamet#computer: ~
0x14E0F46 1 153048 Res Act Ena Vis mshta The HTA Helpomatic -- Presented by the Microsoft Scripting Guys
Modified the answer from here to get this which does what I need:
Add-Type #"
using System;
using System.Runtime.InteropServices;
using System.Text;
public class UserWindows {
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowText(IntPtr hwnd,StringBuilder lpString, int cch);
[DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)]
public static extern Int32 GetWindowTextLength(IntPtr hWnd);
}
"#
while(1) {
$ForgroundWindow = [UserWindows]::GetForegroundWindow()
$FGWTitleLength = [UserWindows]::GetWindowTextLength($ForgroundWindow)
$StringBuilder = New-Object text.stringbuilder -ArgumentList ($FGWTitleLength + 1)
$null = [UserWindows]::GetWindowText($ForgroundWindow,$StringBuilder,$StringBuilder.Capacity)
if ($StringBuilder.ToString() -match $HTAWindowTitleRegEx) {
# Put further scripting here for when the HTA window is in front
}
Start-Sleep -Seconds 1
}
Hope that helps someone.

Powershell script from shortcut to change desktop

Any ideas and suggestions on why this works when run from w/in PS, but not when run from a shortcut defined as:
%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -File "C:\Users\bin\ChangeDesktop.ps1"
Contents of ChangeDesktop.ps1:
set-itemproperty -path "HKCU:Control Panel\Desktop" -name WallPaper -value ""
rundll32.exe user32.dll, UpdatePerUserSystemParameters
If I am in the PS "command prompt" environment the desktop background is automatically removed and refreshed, outside of that I have to manually refresh the desktop to effect the change.
System is Windows Server 2008 R2 - fresh install. Script executionpolicy is set to RemoteSigned, and I don't see any PS errors. I Just don't see the desktop refresh automatically when running from a desktop shortcut.
scratches head
rundll32.exe user32.dll, UpdatePerUserSystemParameters didn't actually change the wallpaper for me on a 2008 x64 box. This does did though... It calls the Win32 API to invoke changing the wallpaper. If you save this as your ChangeDesktop.ps1 script it should work. As it is below it will remove any desktop wallpaper. However if you do want to set one you can edit the last line with the path of a supported image file like this:
[Wallpaper.Setter]::SetWallpaper( 'C:\Wallpaper.bmp', 0 )
The second argument is for the styling:
0: Tile
1: Center
2: Stretch
3: No Change
The script:
Add-Type #"
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32;
namespace Wallpaper
{
public enum Style : int
{
Tile, Center, Stretch, NoChange
}
public class Setter {
public const int SetDesktopWallpaper = 20;
public const int UpdateIniFile = 0x01;
public const int SendWinIniChange = 0x02;
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern int SystemParametersInfo (int uAction, int uParam, string lpvParam, int fuWinIni);
public static void SetWallpaper ( string path, Wallpaper.Style style ) {
SystemParametersInfo( SetDesktopWallpaper, 0, path, UpdateIniFile | SendWinIniChange );
RegistryKey key = Registry.CurrentUser.OpenSubKey("Control Panel\\Desktop", true);
switch( style )
{
case Style.Stretch :
key.SetValue(#"WallpaperStyle", "2") ;
key.SetValue(#"TileWallpaper", "0") ;
break;
case Style.Center :
key.SetValue(#"WallpaperStyle", "1") ;
key.SetValue(#"TileWallpaper", "0") ;
break;
case Style.Tile :
key.SetValue(#"WallpaperStyle", "1") ;
key.SetValue(#"TileWallpaper", "1") ;
break;
case Style.NoChange :
break;
}
key.Close();
}
}
}
"#
[Wallpaper.Setter]::SetWallpaper( '', 0 )
Originally from PoshCode: http://poshcode.org/491
This might sounf weird, but what worked for me was using single quotes instead of double. So it would look like this:
Set-ItemProperty -path "HKCU:Control Panel\Desktop" -name 'wallpaper' -value 'some value'
rundll32.exe user32.dll, UpdatePerUserSystemParameters
This script works wonders. For a domain deployment, we didn't want it to continuously change the background each time a user logs in.
I made the following changes so that it checks to see if the background exists on the computer in the desired location, if it does exist then to exit, if it does not to go ahead with the file copy and set the background.
it first maps the hidden share, copies the file to the desired directory, sets the wallpaper and then disconnects the hidden share. if "X" is already used by your company insert another drive letter. :D
$strFileName="C:\Users\Public\Pictures\background.jpg"
If (Test-Path $strFileName){
# // File exists
Exit-PSSession
}Else{
# // File does not exist
New-PSDrive -Name X -PSProvider Filesystem -Root \\hiddenfileshare\wallpapers
Copy-Item X:\background.jpg C:\Users\Public\Pictures
[Wallpaper.Setter]::SetWallpaper( 'C:\Users\Public\Pictures\background.jpg', 0 )
Remove-PSDrive X
}
The script provided by Andy Arismendi is awesome!
I've used it to make a fun project - set a random wallpaper scraped from the net.
I'm posting it here for anyone interested. Before using it you need to change a few constants at the top of the script source. You also need to download the HtmlAgilityPack.dll library (there are instructions in the script comments).
Enjoy!
P.S. If the wallpaper site I'm using goes down or changes its layout the scraping in the script will go to hell, but nevertheless with my script as an example I bet you'll be able to build another wallpaper scraper.
############## CONSTANTS ##############
# add the library for parsing html - HtmlAgilityPack - download it with nuget from https://www.nuget.org/packages/HtmlAgilityPack
# download nuget command line from https://dist.nuget.org/index.html and install HtmlAgilityPack with "nuget install HtmlAgilityPack" from the cmd
# enter the path to HtmlAgilityPack.dll library used for html parsing
$html_parser_path = "C:\Users\username\Documents\htmlagilitypack\HtmlAgilityPack.1.4.9.5\lib\Net20\HtmlAgilityPack.dll"
# choose where your wallpapers will be downloaded
$wallpaper_dir_path = "C:\Users\username\Pictures\"
# get random wallpaper category from wallpaperscraft.com - the ones below are my favourite categories, edit it if you want to get other categories
<#
you can choose your favorite wallpaper categories from the list below
3D
Abstract
Animals
Anime
Brands
Cars
City
Fantasy
Flowers
Food
Games
Girls
Hi-Tech
Holidays
Macro
Men
Movies
Music
Nature
Other
Space
Sport
Textures
TV Series
Vector
#>
$categories = #("animals","flowers","macro","nature","space")
# I download my wallpapers from the site below - real quality wallpapers
# don't forget to change your resolution - I'm using a 1920x1080 monitor
<#
A list of resolutions to choose from:
1600x1200
1400x1050
1280x1024
1280x960
1152x864
1024x768
3840x2400
3840x2160
3840x1200
2560x1600
2560x1440
2560x1080
2560x1024
2048x1152
1920x1200
1920x1080
1680x1050
1600x900
1440x900
1280x800
1280x720
2160x3840
1440x2560
1366x768
1080x1920
1024x600
960x544
800x1280
800x600
720x1280
540x960
480x854
480x800
400x480
360x640
320x480
320x240
240x400
240x320
2732x2732
2048x2048
1080x1920
1024x1024
750x1334
640x1136
640x960
320x480
1366x768
1920x1080
360x640
1024x768
1600x900
1280x900
1440x900
1280x1024
800x600
1680x1050
2560x1440
320x480
1920x1200
480x800
720x1280
#>
$resolution = "1920x1080" # default resolution
$url = "https://wallpaperscraft.com/catalog/category/$resolution" # category is a placeholder
############## END OF CONSTANT DECLARATIONS ##############
Add-Type #"
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32;
namespace Wallpaper
{
public enum Style : int
{
Tile, Center, Stretch, NoChange
}
public class Setter {
public const int SetDesktopWallpaper = 20;
public const int UpdateIniFile = 0x01;
public const int SendWinIniChange = 0x02;
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern int SystemParametersInfo (int uAction, int uParam, string lpvParam, int fuWinIni);
public static void SetWallpaper ( string path, Wallpaper.Style style ) {
SystemParametersInfo( SetDesktopWallpaper, 0, path, UpdateIniFile | SendWinIniChange );
RegistryKey key = Registry.CurrentUser.OpenSubKey("Control Panel\\Desktop", true);
switch( style )
{
case Style.Stretch :
key.SetValue(#"WallpaperStyle", "2") ;
key.SetValue(#"TileWallpaper", "0") ;
break;
case Style.Center :
key.SetValue(#"WallpaperStyle", "1") ;
key.SetValue(#"TileWallpaper", "0") ;
break;
case Style.Tile :
key.SetValue(#"WallpaperStyle", "1") ;
key.SetValue(#"TileWallpaper", "1") ;
break;
case Style.NoChange :
break;
}
key.Close();
}
}
}
"#
Add-Type -Path $html_parser_path
$rand_index = Get-Random -minimum 0 -maximum $categories.Length
$random_category = $categories[$rand_index]
# replace the placeholder "category" with the random category chosen above
$url = $url -replace "category", $random_category
$doc = New-Object HtmlAgilityPack.HtmlDocument
$doc.LoadHtml((New-Object System.Net.WebClient).DownloadString($url))
# NOTE: the html parser I'm using locates elements by XPath only
$page_links = $doc.DocumentNode.SelectSingleNode("//div[contains(#class, 'pages')]").SelectNodes("a")
# get last page link
$last_page_link = $page_links[$page_links.Count - 1].GetAttributeValue("href", "")
# get last page number
$last_page_number = [regex]::match($last_page_link,'.*page(\d+)').Groups[1].Value
$random_page_number = Get-Random -minimum 0 -maximum $last_page_number
$random_page_addr = ""
# page 1 doesn't add anything to the url
if ($random_page_number -gt 0){
$random_page_addr = "/page$random_page_number"
}
$doc.LoadHtml((New-Object System.Net.WebClient).DownloadString("$url$random_page_addr"))
# get wallpaper divs
$wallpaper_divs = $doc.DocumentNode.SelectNodes("//div[contains(#class, 'wallpaper_pre')]")
$random_wallpaper_div = Get-Random -minimum 0 -maximum 15 # there are 15 wallpapers on a page
# get a sample wallpaper link which has to be substituted later
$sample_wallpaper_link = $wallpaper_divs[$random_wallpaper_div].SelectNodes("a")[0].GetAttributeValue("href", "")
# substitute the above link to get the image link itself
$sample_wallpaper_link = $sample_wallpaper_link -replace "download", "image"
$sample_wallpaper_link = $sample_wallpaper_link -replace "/$resolution", "_$resolution.jpg"
$sample_wallpaper_link = $sample_wallpaper_link -replace "//", "https://"
$wallpaper_image_name = [regex]::match($sample_wallpaper_link,'.*image/(\w+)').Groups[1].Value
$wallpaper_image_name = "$wallpaper_image_name.jpg"
$wc = New-Object System.Net.WebClient
$save_location = "$wallpaper_dir_path$wallpaper_image_name"
$wc.DownloadFile($sample_wallpaper_link, "$save_location")
[Wallpaper.Setter]::SetWallpaper($save_location, 1 )

Get Tfs Shelveset file contents at the command prompt?

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