Při návrhu sloupců primárních klíčů se u MS SQL poměrně často používá datový typ GUID. Jednou ze zajímavých výhod tohoto datového typu pro aplikačního vývojáře je fakt, že hodnoty primárních klíčů je možné generovat i v klientské aplikaci (standardně metodou Guid.NewGuid()). Odpadá tak určitá pracnost navíc při použití databází generovaných sekvenčních hodnot klíče (INT s IDENTITY apod.), kdy je nutné po vložení záznamu hodnoty z databáze do aplikace zpětně načítat. Z pohledu vývojáře je tedy GUID většinou méně pracné řešení a proto bývá také často jako primární klíč používán.
Pro databázový server nicméně primární klíče s datovým type GUID není úplně ideální volba. Datový typ GUID zabírá jednak o něco víc místa než například zmiňovaný INT (16 byte GUID oproti 4 byte INT). Díky tomu budou například databázové indexy s použitím GUID vždy větší, než indexy s použitím INT.
Možná ještě větším problémem může být skutečnost, že generovaná hodnota je z pohledu řazení náhodná. Ve spojení s primárním klíčem a navíc clustered indexem (pokud není explicitně řečeno jinak, je vytvářen s primárním klíčem “by default”) dochází tedy většinou k tomu, že nově vkládaný záznam není ukládán jako u sekvenčních hodnot v tabulce “na konec”, ale do různých datových stránek podle vygenerované hodnoty. Toto vede k častější nutnosti rozdělování stránek a reorganizaci dat v nich (blíže viz popis u Fill Factor). Výsledkem je obvykle daleko vyšší počet použitých stránek a také jejich vyšší fragmentace, než při použití klíčů se sekvenčně generovanými hodnotami.
Jedním z řešení, které dokáže tuto nevýhodu eliminovat, je generování sekvenčních GUID. Jedná se o způsob generování GUID, který zajišťuje, že hodnoty generované na jednom počítači budou jednak unikátní a také každá vytvořená hodnota bude větší než předchozí.
V databázi pro tento způsob vytváření lze využít funkci NEWSEQUENTIALID (bohužel lze využít pouze jako default hodnota sloupce), možnost generování takovéto hodnoty v aplikaci je ukázáno dále.
Vytváření sekvenčního GUID v .NET
Pro vytvoření sekvenčního GUID existuje API funkce UuidCreateSequential. Protože managed třída Guid metody pro její využití neobsahuje, je nutné toto implementovat s pomocí P/Invoke voláním této API funkce. Pomocnou třídu pro jejich vytváření nazveme například GuidCreator a vypadat by mohla například takto:
Code Snippet
- public static class GuidCreator
- {
- private enum RpcUuidCodes : int
- {
- RPC_S_OK = 0,
- RPC_S_UUID_LOCAL_ONLY = 1824,
- RPC_S_UUID_NO_ADDRESS = 1739
- }
- [System.Runtime.InteropServices.DllImport("rpcrt4.dll", SetLastError = true)]
- static extern int UuidCreateSequential(out Guid guid);
-
- public static Guid NewGuid()
- {
- return Guid.NewGuid();
- }
- public static Guid NewSequentialGuid()
- {
- return CreateSequentialGuid();
- }
- public static Guid NewSQLSequentialGuid()
- {
- return SQLLikeReverseBytes(CreateSequentialGuid());
- }
-
- private static Guid CreateSequentialGuid()
- {
- Guid guid = Guid.Empty;
- var resultCode = UuidCreateSequential(out guid);
- switch (resultCode)
- {
- case (int)RpcUuidCodes.RPC_S_OK:
- break;
- case (int)RpcUuidCodes.RPC_S_UUID_LOCAL_ONLY:
- throw new Exception(@"NewGuid failed - UuidCreateSequential returned RPC_S_UUID_LOCAL_ONLY");
- case (int)RpcUuidCodes.RPC_S_UUID_NO_ADDRESS:
- throw new Exception(@"NewGuid failed - UuidCreateSequential returned RPC_S_UUID_NO_ADDRESS");
- default:
- throw new Exception(String.Format(@"NewGuid failed - UuidCreateSequential returned {0}", resultCode));
- }
- return guid;
- }
- private static Guid SQLLikeReverseBytes(Guid value)
- {
- var bytes = value.ToByteArray();
- Array.Reverse(bytes, 0, 4);
- Array.Reverse(bytes, 4, 2);
- Array.Reverse(bytes, 6, 2);
- return new Guid(bytes); ;
- }
- }
Metoda NewGuid je zde uvedena jen pro úplnost a reprezentuje klasické vytváření náhodného GUID pomocí volání standardní .NET metody Guid.NewGuid(). Hodnoty jsou generovány náhodně:
| 965bb964-6eca-4607-a037-d2550b51aefa |
| 6f7d260a-67b3-444b-a7e7-dba8fddcd7a6 |
| 1fecc9c7-50a7-4f78-99cc-469a8cf65fc4 |
| 7fbde154-be15-469a-8d91-c337b9d1c37a |
Metoda NewSequentialGuid reprezentuje vytváření sekvenčního GUID pomocí přímého volání API funkce UuidCreateSequential:
| b5aea086-aecc-11df-940d-001fe2e4f119 |
| b5aea087-aecc-11df-940d-001fe2e4f119 |
| bd77004c-aecc-11df-940d-001fe2e4f119 |
| bd77004d-aecc-11df-940d-001fe2e4f119 |
Pro kompatibilitu s SQL metodou NEWSEQUENTIALID je potřeba ještě ve vygenerovaném GUID provést přehození některých bytů. Dle http://blogs.msdn.com/b/sqlprogrammability/archive/2006/03/23/559061.aspx se přehození provádí proto, aby výsledné hodnoty lépe vyhovovaly interním algoritmům SQL serveru pro porovnávání GUID hodnot. Tento postup implementuje metoda NewSQLSequentialGuid a generované hodnoty tak odpovídají hodnotám vraceným uvedenou SQL funkcí:
| 6cd9ef0e-cdae-df11-940d-001fe2e4f119 |
| 6dd9ef0e-cdae-df11-940d-001fe2e4f119 |
| 4ab28917-cdae-df11-940d-001fe2e4f119 |
| 4bb28917-cdae-df11-940d-001fe2e4f119 |
V případě použití takto generovaných hodnot je samozřejmě dobré si uvědomit, že sekvenčně generované budou hodnoty vždy pouze na jednom počítači. Lze to tedy použít u aplikací, u nichž lze toto zajistit (ASP.NET, vícevrstvé aplikace s aplikačním serverem apod.). V klientských aplikacích bez aplikačního serveru postrádá většinou použití sekvenčních ID smysl.
Testovací aplikace s příkladem generování hodnot pomocí výše uvedené třídy GuidCreator je k dispozici ke stažení zde.
Na závěr ještě pár odkazů na další informace:
http://msdn.microsoft.com/en-us/library/ms190215.aspx
http://www.mssqltips.com/tip.asp?tip=1600
http://www.jorriss.net/blog/jorriss/archive/2008/04/24/unraveling-the-mysteries-of-newsequentialid.aspx
http://www.shirmanov.com/2010/05/generating-newsequentialid-compatible.html
Ve firmě jsme před nějakým časem začali používat na vývojářských počítačích delay signing s následným vytvořením plného podpisu při buildu na TFS build serveru. Nastavení vývojářských počítačů pro podporu delay signingu jsem popisoval v tomto článku . K vytvoření plného podpisu byl použit vlastní build task, který spouští nad každou zkompilovanou assembly nástroj sn.exe. Implementaci tasku a jeho použití jsem se věnoval v dalším článku.
Řešení s pomocí vlastního tasku bylo sice funkční, ale bylo poměrně pomalé a občas nestabilní, zejména při větším zatížení build serveru. Se zvyšujícím se počtem projektů v TFS tyto problémy narůstaly a bylo nutné najít jiné řešení.
Řešením se nakonec ukázalo využití konfiguračního elementu CustomPropertiesForBuild v projektovém souboru TFSBuild.proj. Tento element je možné využít k předání vlastností MSBuildu (+ případnému přepsání existujících) pro build jednotlivých projektů v solution. Výchozí nastavení pro “delay signing” jednotlivých projektů je tak možné přepsat na plné podepsání následujícím způsobem:
<CustomPropertiesForBuild>
SignAssembly=true;DelaySign=false;AssemblyOriginatorKeyFile=c:\Keys\firemniKlic.snk
</CustomPropertiesForBuild>
Za inspiraci díky http://ozgrant.com/2008/03/13/strong-name-your-assemblies-with-team-build-using-a-private-key/ a pokud by někdo chtěl experimentovat s uložením privátního klíče do úložiště klíčů namísto souboru, tak to je možné také – viz http://blogs.msdn.com/nagarajp/archive/2005/11/08/490501.aspx .
Po minulém povídání jakým způsobem lze použít delat signing při vývoji aplikací se dnes podíváme na to jak přimět Team Foundation Server k vytvoření "opravdového" podpisu při buildu, tj. podpisu assembly firemním privátním klíčem.
Požadovaný scénář vytvoření strong name při buildu na serveru
Vývojáři kteří nemají přístup k privátnímu klíči používají na svých stanicích delay signing způsobem popsaným v předchozím příspěvku. Malé doplnění k tomuto - "finta s hvězdičkou" na kterou mě v komentáři k příspěvku navedl Michael Juřek je pro administraci zmiňovaného seznamu assembly u kterých se má přeskakovat kontrola strong name jednodušším řešením než vkládání/mazání jednotlivých assembly. Opravdu nevím jak jsem toto řešení mohl přehlédnout, tento politováníhodný omyl se mi naštěstí stává maximálně jednou za deset let... ;-)
Pro vytváření buildů projektu (pro testování, výslednou distribuci atd.) používáme Team Foundation Build Server. Buildování na serveru probíhá podobně jako u každého vývojáře na stanici (zjednodušeně řečeno). Tj. build server si ze source controlu stáhne zdrojové kódy na disk, ty přeloží a nakopíruje do určeného adresáře. Build server umožňuje samozřejmě provádět při buildu i další akce jako spuštění sady unit testů apod. Předpis podle kterého server build provádí je "klasický" projektový soubor MSBuildu.
V rámci vytvoření buildu na serveru chceme navíc zajistit, aby výsledné assembly byly podepsány firemním privátním klíčem. Projekty které jsou na serveru buildovány mají v sobě ale nastaveno použití delay signingu a soubor s klíčem který je součástí zdrojových souborů v source controlu obsahuje pouze veřejný klíč.
Dostupost privátního klíče se dá vyřešit poměrně snadno, stačí například soubor umístit do souboru na server a k tomuto souboru omezit přístupová práva pouze pro "neběžné" uživatele a samozřejmě také pro uživatele pod kterým je spouštěn proces vytvoření buildu.
Problémem tedy zůstává, jakým způsobem při buildu serveru zajistit, aby nepoužil nastavení jednotlivých projektů pro delay signing (tj. odkaz pouze na veřejný klíč a zapnutý delay signing) ale podepsal assembly privátním klíčem.
Zvolené řešení
Po delším (opět nepříliš úspěšném, žeby další kandidát na politováníhodný omyl jako v případě přehlednuté "finty s hvězdičkou" minule ?) pátrání jsem na Codeplexu našel knihovnu úloh pro MSBuild SDC Tasks Library. Tato knihovna obsahuje celkem slušnou řádku doplňujících úloh které je možné využít v MSBuild projektech. Jednou z těchto úloh je úloha s názvem ReSign, která umí přesně to co potřebujeme - podepsání assembly uvedeným privátním klíčem.
Možnosti přizpůsobení serverového buildu
Proces vytvoření buildu na serveru probíhá podle speciálního MSBuild projektu, který je vygenerován průvodcem Visual Studia při vytváření nového buildu. Tyto projektové soubory jsou uloženy v source control systému daného TFS projektu ve složce s názvem "TeamBuildTypes". Tato složka obsahuje pro každý vytvořený typ buildu podsložku která se jmenuje stejně jako zvolený build. No a v této podsložce (popis složek už vypadá trochu jak "matrjoška", ale teď už jsme naštěstí u konce...) je umístěn mj. soubor TFSBuild.proj, ve kterém je uložen hledaný projekt buildu.
Projekt pro serverový build umožňuje modifikovat proces vytvoření buildu pomocí tzv. customizable targets, což jsou v podstatě předdefinované body buildovacího procesu do kterých je možné vložit vlastní úlohy. Seznam těchto přizpůsobitelných míst včetně jejich popisu je popsán např. zde. Bližšímu popisu možností přizpůsobení buildu se ale budu věnovat možná někdy později, pro dnešek budeme muset vystačit s informací že pro podepsání výsledných assembly privátním klíčem je možné použít například target BeforeDropBuild, který je zavolán před umístěním zkompilovaných assembly do výsledného asdresáře.
Přizpůsobení úlohy ReSign k obrazu svému
Použití úlohy ReSign na projektech s mnoha assembly je trošku těžkopádné na konfiguraci. Tato úloha se totiž snaží o vytvoření strong name u všech specifikovaných assembly bez ohledu na to, zda assembly je pouze delay signed nebo už je plně podepsaná. Solution typicky kromě assembly vzniklých z jednotlivých jejích projektů obsahují různé referencované knihovny s komponentami vlasními nebo třetích stran a tyto assembly jsou při buildu nakopírovány do výsledného adresáře stejně jako assembly jednotlivých projektů. Specifikace seznamu bez možnosti použití "hvězdičkové konvence" (*.*) by byla složitější, pro usnadnění jsem proto ze zdrojových kódů třídu ReSign upravil pro vlastní potřeby tak, aby testovala zda assembly jsou delay signed. Tuto přidanou funkčnost lze zapnout/vypnout parametrem v konfiguraci úlohy (parametr ExcludeAlreadySigned - viz dále).
Úlohu je samozřejmě možné použít i bez této úpravy, nicméně proč si to v rámci možností trošku neusnadnit...
Použití v praxi
Pro použití popisovaného řešení budeme potřebovat editovat soubor TFSBuild.proj. Protože je umístěn v source control systému, je dobré s ním pracovat podobně jako s ostatními zdrojovými kódy - tj. před editací použijeme "Check-out" a po editaci provedené změny potvrdíme do source controlu pomocí "Check-in".
Vlastní konfiguraci podepsání pak do souboru TFSBuild.proj přidáme zhruba takto:
<PropertyGroup>
<BinariesBasePath>$(BinariesRoot)\Release</BinariesBasePath>
</PropertyGroup>
<ItemGroup>
<Assemblies Include="$(BinariesBasePath)\**\*.dll;$(BinariesBasePath)\**\*.exe" />
</ItemGroup>
<UsingTask TaskName="Microsoft.Sdc.Tasks.Tools.StrongName.ReSign" AssemblyFile="c:\program files\msbuild\Microsoft\Microsoft.Sdc.Tasks\bin\Microsoft.Sdc.Tasks.dll" />
<Target Name="BeforeDropBuild">
<Tools.StrongName.ReSign
ContinueOnError="false"
Assemblies="@(Assemblies->'%(FullPath)')"
Runtime="2.0"
KeyFile="c:\Keys\firemniKlic.snk"
ExcludeAlreadySigned="true"
/>
</Target>
Na začátku si vytvoříme vlastní property BinariesBasePath, do které vložíme cestu ve které budou umístěny výsledné assembly.
Seznam těchto assembly poté vytvoříme jako položku Assemblies a díky úpravě třídy ReSign zde můžeme jednoduše použít hvězdičkovou notaci kterou specifikujeme že seznam má obsahovat všechny soubory s příponou .dll nebo .exe ve všech podadresářích cesty BinariesBasePath.
Elementem UsingTask definujeme že máme zájem o použití úlohy ReSign z assembly Microsoft.Sdc.Tasks.dll.
Poté v rámci již zmiňovaného targetu BeforeDropBuild nakonfigurujeme zavolání akce ReSign a pomocí jejích parametrů uvedeme co a jakým způsobemm má být podepsáno:
- Assemblies specifikuje seznam assembly které mají být podepsány.
- Runtime specifikuje jakou verzi SDK má úloha použít, tj. z kterého SDK pokud jich je na PC nainstalováno víc se použije nástroj sn.exe.
- KeyFile obsahuje cestu k souboru s firemním klíčem. Jen připomenu že práva pro přístup k souboru "c:\Keys\firemniKlic.snk" je dobré omezit opravdu jen pro vybrané uživatele, pokud práva ke čtení souboru bude mít každý vývojář tak celá snaha ztrácí tak nějak smysl... ;-)
- ExcludeAlreadySigned specifikuje zda se mají ze seznamu přeskakovat assembly které již mají strong name - jedná se o konfigurace doplněné funkčnosti zmiňované v předchozím textu.
Existuje snazší cesta?
Zvolené řešení je zdá se funkční a pro požadované použití celkem vyhovující, přesto si říkám zda neexistuje opět nějaká snazší cesta kterou jsem (jako obvykle...) přehlédl? Existuje opět nějaká "finta s hvězdičkou", anebo se strong name ve vývoji pracujete úplně jiným způsobem?
Už nějakou dobu ve firmě používáme podepisování assembly pomocí strong name. Před necelým rokem jsme se začali zabývat použitím odloženého podepisování (trošku váhám jak se toto vlastně má správně přeložit - odložený podpis, opožděný podpis nebo nějak jinak?). Základním důvodem k tomuto rozhodnutí je snaha aby "běžní" vývojáři neměli přístup k firemnímu privátnímu klíči. Podle různých doporučení ze strany Microsoftu jsme nabyli dojmu že je to ta "správná" cesta pro použití strong name ve firmě a rozhodli jsme se ji použít. Popis toho jaké nástrahy s sebou použití odloženého podepisování přináší pro práci vývojáře se budu snažit na základě vlastních zkušeností popsat v tomto příspěvku, v pokračování někdy příště se zmíním také o nástrahách které použití přináší ve spojení s Team Foundation Serverem a jeho buildovacím serverem.
Jen malá poznámka na úvod - nebudu se zde příliš zabývat tím, k čemu všemu slouží strong name je a k čemu konkrétně odloženého podepisování. Zájemce o detaily k této problematice odkážu na kompetentní zdroje v MSDN.
Příprava na použití - začínáme
Pro podepisování assembly pomocí strong name je potřeba pomocí nástroje sn.exe vygenerovat pár klíčů (privátní a veřejný jak bývá zvykem). To lze provést zhruba takto:
sn.exe -k firemniKlic.snk
Pokud chceme privátní klíč před běžnými vývojáři schovat, provedeme vyextrahování veřejného klíče z tohoto souboru pomocí:
sn.exe -p firemniKlic.snk firemniKlicPublic.snk
a původní soubor firemniKlic.snk uložíme někam, kam nemají běžní vývojáří přístup. Měli by tam ale mít samozřejmě přístup alespoň ti "neběžní", aby mohli před distribucí assembly "opravdicky" podepsat. Těm běžným poté dáme pouze veřejný klíč uložený v tuto chvíli v souboru firemniKlicPublic.snk a jak odpovědět na dotazy vývojářů typu "A co s tím teď jako mám dělat?" si popíšeme dále.
Nastavení opožděného podepisování ve zdrojácích .NET projektu
Nastavení podepisování assembly pomocí strong name se provádí pomocí atributu AssemblyKeyFileAttribute, kterým určíme soubor ve kterém je uložen klíč pro vytvoření strong name. Jen pro úplnost - existuje i možnost klíče místo ze souboru brát z CSP kontejneru, k tomu lze využít atribut AssemblyKeyNameAttribute. V našem případě používáme umístění klíče v souboru, protože se nám toto jeví jako praktičtější kvůli umístění souboru do source control systému společně se zdrojáky. Vývojář kromě stažení zdrojových kódů z TFSka nemusí pro práci na projektu na svém PC dělat nic jiného
(umisťování klíčů do CSP kontejneru apod.).
Použití odloženého podepsání se následně specifikuje atributem AssemblyDelaySignAttribute a jeho parametrem true/false. Oba tyto atributy se samozřejmě váží k assembly. Ve VS 2002/2003 je tedy dobrým zvykem je umístit "ručně" do souboru AssemblyInfo.cs:
[assembly: AssemblyKeyFile("firemniKlicPublic.snk")]
[assembly: AssemblyDelaySign(true)]
Ve VS 2005 se nastavení provádí ve vlastnostech projektu na záložce Signing - volby "Sign the assembly" a "Delay sign only".
Poté co toto nastavení provedeme, projekt je možné bez problémů přeložit a jako vývojáři jsme spokojení jak jsme to všechno skvěle zvládli a jak nám to odložený podepisování krásně bude teď fungovat. Ale bohužel tak jednoduché to zase není, protože problém se většinou dostaví hned poté co budeme chtít takto nastavené projekty spouštět a ladit.
Použití delay-signed assembly
Pro .NET Runtime představuje delay-signed assembly problém, bez dalšího nastavení ji odmítne použít. Vývojář je o tomto většinou informován hned při pokusu o ladění aplikace v debuggeru VS (nejpozději při pokusu o spuštění aplikace mimo VS, pokud debugger nepoužije) hláškou "Error while trying to run project: Could not load file or asembly...". Tato hláška je výsledkem pokusu o ověření platnosti strong name při nahrávání assembly a protože podpis nebyl vytvořen privátním klíčem tak toto ověření bohužel selže.
Problém je možné vyřešit tím, že se tato assembly zaregistruje do seznamu assembly u kterých nemá být ověření platnosti strong name prováděno. Použijeme k tomu opět nástroj sn.exe:
Po zařazení assembly do seznamu nebude runtime při nahrávání verifikaci provádět a assembly bez dalšího protestování nahraje. (Pokud je spuštěno VS a assembly do seznamu umístěna až poté co jsme se ji pokusili použít a nastala výše uvedená chyba, "zabere" zařazení assembly do seznamu až po restartu VS. Vypadá to na nějakou formu cachování načtených assembly ve VS, ale nijak blíž jsem tuto domněnku nezkoumal...)
Jak pozorný čtenář asi tuší, vyřazení kontroly strong name u assembly představuje určité bezpečnostní riziko. Pokud nebude strong name při nahrávání assembly kontrolován, může kdokoliv místo původní assembly podstrčit svoji upravenou (stačí mu k tomu náš veřejný klíč). Proto je dobré tento příkaz používat pouze na počítačích vývojářů nebo případně na testovacím stroji, nikdy v produkčním prostředí.
Pro práci se seznamem nástroj nabízí samozřejmě i další funkce. K vyjmutí assembly ze seznamu slouží příkaz
a k případnému úplnému vyčištění seznamu (ve stylu "MrProper") příkaz
Poté co assembly zařadíme do seznamu a runtime přestane její strong name ověřovat, je možné na počítači vývojáře tyto assembly vesele používat. Pokud ale tyto assembly poté chceme distribuovat jako finální produkt zákazníkovi, musíme zajistit jejich "plnokrevné" podepsání. To typicky bude provádě onen "neběžný" vývojář, který má přístup k privátnímu klíči spuštěním známého nástroje sn.exe:
sn.exe -Ra assembly.dll firemniKlic.snk
Usnadnění práce - aneb příkazový řádek ano, ale... 
Snaha o použití strong name a odloženého podepisování při vývoji je nyní korunována úspěchem a bylo by možné zde skončit. Nicméně vývojáři často pracují se solution které čítají několik (nebo i několik desítek) projektů a z tohoto způsobu použití by nebyli asi nijak zvlášť nadšeni. Představa že musí pro každou assembly na svém počítači spustit z příkazové řádky sn.exe s příslušnými parametry a poté případně metodou pokus-omyl za neustálého restartování VS zjišťovat kterou že assembly ještě zapoměli do seznamu přidat není příliš lákavá. Na toto by se hodil nějaký nástroj, který by umožnil tuto činnost nějak zjednodušit.
Přiznám se rovnou že se mi žádný podobný nástroj od MS ani třetích stran nepodařilo najít, tak jsem to nakonec vyřešil tak že jsem si jej napsal sám. Je to jednoduchá WinForm aplikace která je integrovaná do VS jako externí nástroj a po spuštění vyhledá rekurzivně v podadresářích solution všechny delay-signed assembly a umožní jejich zařazení do seznamu. Stejně tak umožňuje poté seznam mazat apod. Není to žádná velká sláva, ale oproti ručnímu spouštění nástroje z příkazového řádku je to pro vývojáře přece jen o chlup jednodušší.
Vynález kola?
Docela by mě zajímalo jaké zkušenosti s problematikou mají ostatní firmy. Používají strong name a pokud ano tak používají ten snazší scénář kdy mají všichni vývojáří přístup k privátnímu klíči anebo jdou jako my složitější cestou použití odloženého podepisování? A pokud někdo toto používá, našel snazší cestu než popisuji (aneb vynalézali jsme znovu kolo)?
Příště se jak jsem avizoval podělím o zkušenosti se zprovozněním opětovného podepsání delay-signed assembly privátním klíčem při buildu na Team Foundation Serveru. Buď jsem opět něco přehlédl (a znovu vynalézal kolo), nebo podpora pro tento scénář opět od MS zcela chybí.