Domanda

Al momento ho un'app che mostra il numero di build nella finestra del titolo.Va bene, tranne per il fatto che non significa nulla per la maggior parte degli utenti, che vogliono sapere se hanno l'ultima build - tendono a riferirsi ad essa come "giovedì scorso" piuttosto che build 1.0.8.4321.

Il piano è invece di inserire lì la data di creazione, ad esempio "App creata il 21/10/2009".

Faccio fatica a trovare un modo programmatico per estrarre la data di compilazione come stringa di testo da utilizzare in questo modo.

Per il numero di build, ho usato:

Assembly.GetExecutingAssembly().GetName().Version.ToString()

dopo aver definito come sono venuti fuori.

Mi piacerebbe qualcosa del genere per la data di compilazione (e l'ora, per i punti bonus).

Suggerimenti qui molto apprezzati (scusate il gioco di parole se appropriato) o soluzioni più ordinate...

È stato utile?

Soluzione

Jeff Atwood aveva alcune cose da dire su questo problema in Determinare Build Date nel modo più duro .

Il metodo più affidabile risulta essere il recupero del timestamp del linker dalla intestazione PE incorporato nel file eseguibile - un po 'di codice C # (di Joe Spivey) per quello dai commenti all'articolo di Jeff:

public static DateTime GetLinkerTime(this Assembly assembly, TimeZoneInfo target = null)
{
    var filePath = assembly.Location;
    const int c_PeHeaderOffset = 60;
    const int c_LinkerTimestampOffset = 8;

    var buffer = new byte[2048];

    using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
        stream.Read(buffer, 0, 2048);

    var offset = BitConverter.ToInt32(buffer, c_PeHeaderOffset);
    var secondsSince1970 = BitConverter.ToInt32(buffer, offset + c_LinkerTimestampOffset);
    var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

    var linkTimeUtc = epoch.AddSeconds(secondsSince1970);

    var tz = target ?? TimeZoneInfo.Local;
    var localTime = TimeZoneInfo.ConvertTimeFromUtc(linkTimeUtc, tz);

    return localTime;
}

Esempio di utilizzo:

var linkTimeLocal = Assembly.GetExecutingAssembly().GetLinkerTime();

AGGIORNAMENTO: il metodo funzionava per .Net Core 1.0, ma ha smesso di funzionare dopo .Net Core 1.1 (fornisce anni casuali nell'intervallo 1900-2020)

Altri suggerimenti

Aggiungi sotto alla riga di comando dell'evento pre-build:

echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"

Aggiungi questo file come risorsa, ora hai una stringa "BuildDate" nelle tue risorse.

Per creare risorse, vedere Come creare e utilizzare le risorse in. NET .

Il modo

Come sottolineato da @ c00000fd nei commenti . Microsoft sta cambiando questo. E mentre molte persone non usano l'ultima versione del loro compilatore, sospetto che questa modifica renda indiscutibilmente questo approccio. E mentre è un esercizio divertente, consiglierei alle persone di incorporare semplicemente una data di costruzione nel loro binario con qualsiasi altro mezzo necessario se è importante tenere traccia della data di costruzione del binario stesso.

Questo può essere fatto con una banale generazione di codice che probabilmente è già il primo passo nello script di compilazione. Questo e il fatto che gli strumenti ALM / Build / DevOps aiutano molto in questo e dovrebbero essere preferiti a qualsiasi altra cosa.

Lascio il resto di questa risposta qui solo per scopi storici.

Il nuovo modo

Ho cambiato idea su questo e attualmente uso questo trucco per ottenere la data di costruzione corretta.

#region Gets the build date and time (by reading the COFF header)

// http://msdn.microsoft.com/en-us/library/ms680313

struct _IMAGE_FILE_HEADER
{
    public ushort Machine;
    public ushort NumberOfSections;
    public uint TimeDateStamp;
    public uint PointerToSymbolTable;
    public uint NumberOfSymbols;
    public ushort SizeOfOptionalHeader;
    public ushort Characteristics;
};

static DateTime GetBuildDateTime(Assembly assembly)
{
    var path = assembly.GetName().CodeBase;
    if (File.Exists(path))
    {
        var buffer = new byte[Math.Max(Marshal.SizeOf(typeof(_IMAGE_FILE_HEADER)), 4)];
        using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read))
        {
            fileStream.Position = 0x3C;
            fileStream.Read(buffer, 0, 4);
            fileStream.Position = BitConverter.ToUInt32(buffer, 0); // COFF header offset
            fileStream.Read(buffer, 0, 4); // "PE\0\0"
            fileStream.Read(buffer, 0, buffer.Length);
        }
        var pinnedBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        try
        {
            var coffHeader = (_IMAGE_FILE_HEADER)Marshal.PtrToStructure(pinnedBuffer.AddrOfPinnedObject(), typeof(_IMAGE_FILE_HEADER));

            return TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1) + new TimeSpan(coffHeader.TimeDateStamp * TimeSpan.TicksPerSecond));
        }
        finally
        {
            pinnedBuffer.Free();
        }
    }
    return new DateTime();
}

#endregion

Alla vecchia maniera

Bene, come si generano i numeri di build? Visual Studio (o il compilatore C #) fornisce effettivamente numeri di build e revisione automatici se si modifica l'attributo AssemblyVersion in ad es. 1.0.*

Ciò che accadrà è che la build sarà uguale al numero di giorni dall'1 gennaio 2000 ora locale e che la revisione sarà uguale al numero di secondi dall'ora locale di mezzanotte, diviso per 2.

vedi Contenuti della community, Numeri di build e revisione automatici

es. AssemblyInfo.cs

[assembly: AssemblyVersion("1.0.*")] // important: use wildcard for build and revision numbers!

SampleCode.cs

var version = Assembly.GetEntryAssembly().GetName().Version;
var buildDateTime = new DateTime(2000, 1, 1).Add(new TimeSpan(
TimeSpan.TicksPerDay * version.Build + // days since 1 January 2000
TimeSpan.TicksPerSecond * 2 * version.Revision)); // seconds since midnight, (multiply by 2 to get original)

Aggiungi sotto alla riga di comando dell'evento pre-build:

echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"

Aggiungi questo file come risorsa, ora hai la stringa 'BuildDate' nelle tue risorse.

Dopo aver inserito il file nella risorsa (come file di testo pubblico), ho effettuato l'accesso tramite

string strCompTime = Properties.Resources.BuildDate;

Per creare risorse, vedere Come creare e utilizzare le risorse in. NET .

Un approccio che mi stupisco che nessuno abbia ancora citato è di usare Modelli di testo T4 per la generazione di codice.

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System" #>
<#@ output extension=".g.cs" #>
using System;
namespace Foo.Bar
{
    public static partial class Constants
    {
        public static DateTime CompilationTimestampUtc { get { return new DateTime(<# Write(DateTime.UtcNow.Ticks.ToString()); #>L, DateTimeKind.Utc); } }
    }
}

Pro:

  • Locale indipendente
  • Permette molto di più del semplice tempo di compilazione

Contro:

Per quanto riguarda la tecnica di estrazione delle informazioni sulla data / versione di build dai byte di un'intestazione PE assembly, Microsoft ha modificato i parametri di build predefiniti a partire da Visual Studio 15.4. La nuova impostazione predefinita include la compilazione deterministica, che rende un timestamp valido e numeri di versione incrementati automaticamente un ricordo del passato. Il campo timestamp è ancora presente ma viene riempito con un valore permanente che è un hash di qualcosa o altro, ma non alcuna indicazione del tempo di creazione.

Alcuni dettagli dettagliati qui

Per coloro che danno la priorità a un utile timestamp rispetto alla compilazione deterministica, esiste un modo per sovrascrivere il nuovo valore predefinito. È possibile includere un tag nel file .csproj dell'assembly di interesse come segue:

  <PropertyGroup>
      ...
      <Deterministic>false</Deterministic>
  </PropertyGroup>

Aggiornamento: Approvo la soluzione del modello di testo T4 descritta in un'altra risposta qui. L'ho usato per risolvere il mio problema in modo pulito senza perdere il beneficio della compilazione deterministica. Un avvertimento a riguardo è che Visual Studio esegue il compilatore T4 solo quando viene salvato il file .tt, non al momento della compilazione. Questo può essere imbarazzante se si esclude il risultato .cs dal controllo del codice sorgente (poiché si prevede che venga generato) e un altro sviluppatore verifica il codice. Senza salvare, non avranno il file .cs. C'è un pacchetto su nuget (credo che si chiama AutoT4) che rende la compilazione T4 parte di ogni build. Non ho ancora affrontato la soluzione a questo durante l'implementazione della produzione, ma mi aspetto qualcosa di simile per farlo bene.

Sono solo un novizio di C #, quindi forse la mia risposta sembra sciocca - Visualizzo la data di costruzione dalla data in cui il file eseguibile è stato scritto l'ultima volta:

string w_file = "MyProgram.exe"; 
string w_directory = Directory.GetCurrentDirectory();

DateTime c3 =  File.GetLastWriteTime(System.IO.Path.Combine(w_directory, w_file));
RTB_info.AppendText("Program created at: " + c3.ToString());

Ho provato ad usare il metodo File.GetCreationTime ma ho ottenuto strani risultati: la data dal comando era 29/05/2012, ma la data da Esplora finestre mostrava 23/05/2012. Dopo aver cercato questa discrepanza ho scoperto che il file è stato probabilmente creato il 23/05/2012 (come mostrato da Esplora risorse), ma copiato nella cartella corrente il 29/05/2012 (come mostrato dal comando File.GetCreationTime) - così per essere al sicuro sto usando il comando File.GetLastWriteTime.

Zalek

Per chiunque abbia bisogno di ottenere il tempo di compilazione in Windows 8 / Windows Phone 8:

    public static async Task<DateTimeOffset?> RetrieveLinkerTimestamp(Assembly assembly)
    {
        var pkg = Windows.ApplicationModel.Package.Current;
        if (null == pkg)
        {
            return null;
        }

        var assemblyFile = await pkg.InstalledLocation.GetFileAsync(assembly.ManifestModule.Name);
        if (null == assemblyFile)
        {
            return null;
        }

        using (var stream = await assemblyFile.OpenSequentialReadAsync())
        {
            using (var reader = new DataReader(stream))
            {
                const int PeHeaderOffset = 60;
                const int LinkerTimestampOffset = 8;

                //read first 2048 bytes from the assembly file.
                byte[] b = new byte[2048];
                await reader.LoadAsync((uint)b.Length);
                reader.ReadBytes(b);
                reader.DetachStream();

                //get the pe header offset
                int i = System.BitConverter.ToInt32(b, PeHeaderOffset);

                //read the linker timestamp from the PE header
                int secondsSince1970 = System.BitConverter.ToInt32(b, i + LinkerTimestampOffset);

                var dt = new DateTimeOffset(1970, 1, 1, 0, 0, 0, DateTimeOffset.Now.Offset) + DateTimeOffset.Now.Offset;
                return dt.AddSeconds(secondsSince1970);
            }
        }
    }

Per chiunque abbia bisogno di ottenere il tempo di compilazione in Windows Phone 7:

    public static async Task<DateTimeOffset?> RetrieveLinkerTimestampAsync(Assembly assembly)
    {
        const int PeHeaderOffset = 60;
        const int LinkerTimestampOffset = 8;            
        byte[] b = new byte[2048];

        try
        {
            var rs = Application.GetResourceStream(new Uri(assembly.ManifestModule.Name, UriKind.Relative));
            using (var s = rs.Stream)
            {
                var asyncResult = s.BeginRead(b, 0, b.Length, null, null);
                int bytesRead = await Task.Factory.FromAsync<int>(asyncResult, s.EndRead);
            }
        }
        catch (System.IO.IOException)
        {
            return null;
        }

        int i = System.BitConverter.ToInt32(b, PeHeaderOffset);
        int secondsSince1970 = System.BitConverter.ToInt32(b, i + LinkerTimestampOffset);
        var dt = new DateTimeOffset(1970, 1, 1, 0, 0, 0, DateTimeOffset.Now.Offset) + DateTimeOffset.Now.Offset;
        dt = dt.AddSeconds(secondsSince1970);
        return dt;
    }

NOTA: in tutti i casi in cui sei in esecuzione in una sandbox, quindi sarai in grado di ottenere solo il tempo di compilazione degli assembly distribuiti con la tua app. (ad es. questo non funzionerà su nulla nel GAC).

Il metodo sopra può essere ottimizzato per gli assembly già caricati all'interno del processo utilizzando l'immagine del file in memoria (invece di rileggerlo dalla memoria):

using System;
using System.Runtime.InteropServices;
using Assembly = System.Reflection.Assembly;

static class Utils
{
    public static DateTime GetLinkerDateTime(this Assembly assembly, TimeZoneInfo tzi = null)
    {
        // Constants related to the Windows PE file format.
        const int PE_HEADER_OFFSET = 60;
        const int LINKER_TIMESTAMP_OFFSET = 8;

        // Discover the base memory address where our assembly is loaded
        var entryModule = assembly.ManifestModule;
        var hMod = Marshal.GetHINSTANCE(entryModule);
        if (hMod == IntPtr.Zero - 1) throw new Exception("Failed to get HINSTANCE.");

        // Read the linker timestamp
        var offset = Marshal.ReadInt32(hMod, PE_HEADER_OFFSET);
        var secondsSince1970 = Marshal.ReadInt32(hMod, offset + LINKER_TIMESTAMP_OFFSET);

        // Convert the timestamp to a DateTime
        var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        var linkTimeUtc = epoch.AddSeconds(secondsSince1970);
        var dt = TimeZoneInfo.ConvertTimeFromUtc(linkTimeUtc, tzi ?? TimeZoneInfo.Local);
        return dt;
    }
}

L'opzione non discussa qui è quella di inserire i propri dati in AssemblyInfo.cs, il " AssemblyInformationalVersion " il campo sembra appropriato - abbiamo un paio di progetti in cui stavamo facendo qualcosa di simile come un passo di costruzione (tuttavia non sono del tutto soddisfatto del modo in cui funziona, quindi non voglio davvero riprodurre ciò che abbiamo).

C'è un articolo sull'argomento su codeproject: http://www.codeproject.com /KB/dotnet/Customizing_csproj_files.aspx

Per i progetti .NET Core, ho adattato la risposta di Postlagerkarte per aggiornare il campo Copyright dell'assembly con la data di compilazione.

Modifica direttamente csproj

Direttamente al primo si può aggiungere quanto segue PropertyGroup nel csproj:

<Copyright>Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))</Copyright>

Alternativa:Proprietà del progetto di Visual Studio

Oppure incolla l'espressione interna direttamente nel campo Copyright nella sezione Pacchetto delle proprietà del progetto in Visual Studio:

Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))

Ciò può creare un po' di confusione, perché Visual Studio valuterà l'espressione e visualizzerà il valore corrente nella finestra, ma aggiornerà anche il file di progetto in modo appropriato dietro le quinte.

A livello di soluzione tramite Directory.Build.props

Puoi far cadere il <Copyright> elemento sopra in a Directory.Build.props nella root della soluzione e applicalo automaticamente a tutti i progetti all'interno della directory, presupponendo che ciascun progetto non fornisca il proprio valore di copyright.

<Project>
 <PropertyGroup>
   <Copyright>Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))</Copyright>
 </PropertyGroup>
</Project>

Directory.Build.props: Personalizza la tua costruzione

Produzione

L'espressione di esempio ti darà un copyright come questo:

Copyright © 2018 Travis Troyer (2018-05-30T14:46:23)

Recupero

Puoi visualizzare le informazioni sul copyright dalle proprietà del file in Windows o acquisirle in fase di esecuzione:

var version = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location);

Console.WriteLine(version.LegalCopyright);

Molte ottime risposte qui, ma mi sento di poter aggiungere le mie per semplicità, prestazioni (rispetto alle soluzioni relative alle risorse) multipiattaforma (funziona anche con Net Core) ed evitamento di qualsiasi strumento di terze parti. Aggiungi questo target msbuild a csproj.

<Target Name="Date" BeforeTargets="CoreCompile">
    <WriteLinesToFile File="$(IntermediateOutputPath)gen.cs" Lines="static partial class Builtin { public static long CompileTime = $([System.DateTime]::UtcNow.Ticks) %3B }" Overwrite="true" />
    <ItemGroup>
        <Compile Include="$(IntermediateOutputPath)gen.cs" />
    </ItemGroup>
</Target>

e ora hai Builtin.CompileTime o new DateTime(Builtin.CompileTime, DateTimeKind.Utc) se ne hai bisogno in questo modo.

A ReSharper non piacerà. Puoi ignorarlo o aggiungere anche una classe parziale al progetto ma funziona comunque.

Nel 2018 alcune delle soluzioni di cui sopra non funzionano più o non funzionano con .NET Core.

Uso il seguente approccio che è semplice e funziona per il mio progetto .NET Core 2.0.

Aggiungi quanto segue al tuo .csproj all'interno del gruppo di proprietà:

    <Today>$([System.DateTime]::Now)</Today>

Questo definisce un PropertyFunction a cui puoi accedere da comando pre-build.

La tua pre-build è simile a questa

echo $(today) > $(ProjectDir)BuildTimeStamp.txt

Imposta la proprietà di BuildTimeStamp.txt su risorsa incorporata.

Ora puoi leggere il timestamp in questo modo

public static class BuildTimeStamp
    {
        public static string GetTimestamp()
        {
            var assembly = Assembly.GetEntryAssembly(); 

            var stream = assembly.GetManifestResourceStream("NamespaceGoesHere.BuildTimeStamp.txt");

            using (var reader = new StreamReader(stream))
            {
                return reader.ReadToEnd();
            }
        }
    }

Avevo bisogno di una soluzione universale che funzionasse con un progetto NETStandard su qualsiasi piattaforma (iOS, Android e Windows.) A tale scopo, ho deciso di generare automaticamente un file CS tramite uno script PowerShell. Ecco lo script di PowerShell:

param($outputFile="BuildDate.cs")

$buildDate = Get-Date -date (Get-Date).ToUniversalTime() -Format o
$class = 
"using System;
using System.Globalization;

namespace MyNamespace
{
    public static class BuildDate
    {
        public const string BuildDateString = `"$buildDate`";
        public static readonly DateTime BuildDateUtc = DateTime.Parse(BuildDateString, null, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
    }
}"

Set-Content -Path $outputFile -Value $class

Salvare il file PowerScript come GenBuildDate.ps1 e aggiungerlo al progetto. Infine, aggiungi la seguente riga al tuo evento pre-build:

powershell -File $(ProjectDir)GenBuildDate.ps1 -outputFile $(ProjectDir)BuildDate.cs

Assicurati che BuildDate.cs sia incluso nel tuo progetto. Funziona come un campione su qualsiasi sistema operativo!

Faccio solo:

File.GetCreationTime(GetType().Assembly.Location)

Puoi utilizzare questo progetto: https://github.com/dwcullop/BuildInfo

Sfrutta T4 per automatizzare il timestamp della data di costruzione. Esistono diverse versioni (diversi rami) tra cui una che ti dà il Git Hash del ramo attualmente estratto, se ti interessa quel genere di cose.

Divulgazione: ho scritto il modulo.

È possibile utilizzare un evento post-build del progetto per scrivere un file di testo nella directory di destinazione con il datetime corrente. È quindi possibile leggere il valore in fase di esecuzione. È un po 'confuso, ma dovrebbe funzionare.

Non ne sono sicuro, ma forse il Build Incrementer aiuta.

Un approccio diverso, compatibile con PCL, sarebbe quello di utilizzare un'attività in linea di MSBuild per sostituire il tempo di compilazione in una stringa restituita da una proprietà sull'app. Stiamo usando questo approccio con successo in un'app che ha progetti Xamarin.Forms, Xamarin.Android e Xamarin.iOS.

Modifica

Semplificato spostando tutta la logica nel file SetBuildDate.targets e usando Regex invece della semplice sostituzione della stringa in modo che il file possa essere modificato da ogni build senza un " reset " .

La definizione dell'attività inline di MSBuild (salvata in un file SetBuildDate.targets locale nel progetto Xamarin.Forms per questo esempio):

<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' ToolsVersion="12.0">

  <UsingTask TaskName="SetBuildDate" TaskFactory="CodeTaskFactory" 
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v12.0.dll">
    <ParameterGroup>
      <FilePath ParameterType="System.String" Required="true" />
    </ParameterGroup>
    <Task>
      <Code Type="Fragment" Language="cs"><![CDATA[

        DateTime now = DateTime.UtcNow;
        string buildDate = now.ToString("F");
        string replacement = string.Format("BuildDate => \"{0}\"", buildDate);
        string pattern = @"BuildDate => ""([^""]*)""";
        string content = File.ReadAllText(FilePath);
        System.Text.RegularExpressions.Regex rgx = new System.Text.RegularExpressions.Regex(pattern);
        content = rgx.Replace(content, replacement);
        File.WriteAllText(FilePath, content);
        File.SetLastWriteTimeUtc(FilePath, now);

   ]]></Code>
    </Task>
  </UsingTask>

</Project>

Richiamare l'attività incorporata sopra nel file csproj Xamarin.Forms nella destinazione BeforeBuild:

  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
       Other similar extension points exist, see Microsoft.Common.targets.  -->
  <Import Project="SetBuildDate.targets" />
  <Target Name="BeforeBuild">
    <SetBuildDate FilePath="$(MSBuildProjectDirectory)\BuildMetadata.cs" />
  </Target>

La proprietà FilePath è impostata su un BuildMetadata.cs file nel progetto Xamarin.Forms che contiene una classe semplice con una proprietà stringa BuildDate, in cui verrà sostituito il tempo di compilazione:

public class BuildMetadata
{
    public static string BuildDate => "This can be any arbitrary string";
}

Aggiungi questo file <=> al progetto. Sarà modificato da ogni build, ma in modo da consentire build ripetute (sostituzioni ripetute), quindi puoi includerlo o ometterlo nel controllo del codice sorgente come desiderato.

Un piccolo aggiornamento sulla quot &; New Way " risposta di Jhon.

È necessario creare il percorso invece di utilizzare la stringa CodeBase quando si lavora con ASP.NET/MVC

    var codeBase = assembly.GetName().CodeBase;
    UriBuilder uri = new UriBuilder(codeBase);
    string path = Uri.UnescapeDataString(uri.Path);

È possibile avviare un passaggio aggiuntivo nel processo di generazione che scrive un timbro data in un file che può quindi essere visualizzato.

Nella scheda delle proprietà dei progetti guarda la scheda degli eventi di compilazione. C'è un'opzione per eseguire un comando pre o post build.

Ho usato il suggerimento di Abdurrahim. Tuttavia, sembrava dare un formato orario strano e ha anche aggiunto l'abbreviazione del giorno come parte della data di costruzione; esempio: dom 24/12/2017 13: 21: 05.43. Avevo solo bisogno della data, quindi ho dovuto eliminare il resto usando la sottostringa.

Dopo aver aggiunto echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt" all'evento pre-build, ho appena fatto quanto segue:

string strBuildDate = YourNamespace.Properties.Resources.BuildDate;
string strTrimBuildDate = strBuildDate.Substring(4).Remove(10);

La buona notizia qui è che ha funzionato.

Se questa è un'app di Windows, puoi semplicemente usare il percorso eseguibile dell'applicazione: nuovo System.IO.FileInfo (Application.ExecutablePath) .LastWriteTime.ToString (" yyyy.MM.dd ")

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top