El trabajo en el equipo de Gestión de la configuración está asociado a garantizar la funcionalidad de los procesos de compilación: ensamblar productos de la empresa, verificación preliminar del código, análisis estadístico, documentación y mucho más. Además, trabajamos constantemente para optimizar diversos procesos y, lo que es maravilloso, somos prácticamente libres de elegir herramientas para este interesante trabajo. A continuación, hablaré en detalle sobre cómo, teniendo solo un nivel diferente de conocimiento en C # y C ++, hice un servicio WCF funcional para trabajar con colas de arreglos. Y por qué decidí que es muy importante.

Automatización una vez o instrucción de 117 páginas una y otra vez
Una pequeña digresión para que entiendas por qué estoy tan preocupado por la automatización y la optimización de los procesos.
Antes de Veeam, trabajaba para una gran empresa internacional: era el líder del equipo del equipo de Gestión de la configuración, estaba involucrado en la creación de la aplicación y su implementación en entornos de prueba. El programa se desarrolló con éxito, se agregaron nuevas funciones, se escribió la documentación, cuyo soporte también traté. Pero siempre me sorprendió por qué un programa tan serio no tiene un sistema de configuración de parámetros normal, de los cuales había muchas docenas, si no cientos.
Hablé con los desarrolladores sobre este tema y recibí una respuesta: el cliente no pagó por esta función, no estuvo de acuerdo con su costo, por lo que la función no se implementó. Pero, de hecho, el control de calidad sufrió, y nosotros, el equipo de SM, fuimos directamente afectados. La configuración del programa y su configuración preliminar se llevó a cabo a través de muchos archivos de configuración, cada uno de los cuales tenía docenas de parámetros.
Cada nueva compilación, cada nueva versión hizo sus cambios en la configuración. Los archivos de configuración antiguos no se podían usar, ya que a menudo eran incompatibles con la nueva versión. Como resultado, cada vez antes de implementar la compilación para la prueba o en las máquinas de trabajo de los probadores, tenía que pasar mucho tiempo configurando el programa, arreglando errores de configuración, consultas constantes con los desarrolladores sobre el tema "¿por qué no funciona así ahora?" En general, el proceso fue extremadamente inoportuno.
Para ayudar con la configuración, tuvimos una instrucción de 117 páginas en tamaño de fuente Arial 9. Tuvimos que leer muy, muy cuidadosamente. A veces parecía que era más fácil construir un kernel de Linux con los ojos cerrados en una computadora apagada.
Quedó claro que la optimización no podía evitarse aquí. Comencé a escribir mi configurador para un programa con soporte para perfiles y la capacidad de cambiar los parámetros en unos segundos, pero el proyecto llegó a su etapa final, y pasé a trabajar en otro proyecto. En él, analizamos muchos registros de un sistema de facturación para detectar posibles errores en el lado del servidor. La automatización de muchas acciones usando el lenguaje Python me salvó de la monstruosa cantidad de trabajo manual. Realmente me gustó este lenguaje de secuencias de comandos, y con su ayuda hicimos un conjunto de secuencias de comandos de análisis para todas las ocasiones. Esas tareas que requirieron varios días de análisis reflexivo de acuerdo con el "cat logfile123 | grep something_special ”, tomó unos minutos. Todo fue genial ... y aburrido.
Gestión de configuración - Nuevas aventuras
Vine a Veeam como líder de equipo para un pequeño equipo de CM. Muchos procesos requieren automatización, optimización y replanteamiento. ¡Pero había total libertad para elegir herramientas! El desarrollador debe usar un lenguaje de programación específico, estilo de código, un conjunto específico de bibliotecas. SM, por otro lado, puede no usar nada para resolver la tarea, si tiene suficiente tiempo, coraje y paciencia para esto.
Veeam, como muchas otras compañías, tiene la tarea de reunir actualizaciones de productos. La actualización incluyó cientos de archivos, y fue necesario cambiar solo aquellos que cambiaron, teniendo en cuenta una serie de condiciones importantes. Para hacer esto, creamos un voluminoso script de PowerShell que podría subir a TFS, seleccionar archivos, ordenarlos en las carpetas necesarias. La funcionalidad del script se complementó, gradualmente se hizo enorme, tomó mucho tiempo depurar y constantemente algunas muletas para arrancar. Era urgente hacer algo.
¿Qué querían los desarrolladores?
Aquí están las principales quejas:
- No se pueden poner en cola las correcciones. Debe verificar constantemente la página web para ver cuándo finaliza el ensamblaje del arreglo privado y puede comenzar a construir el suyo.
- No hay notificaciones sobre errores: para ver los errores en la GUI de la aplicación de ensamblaje, debe ir al servidor y observar muchos registros voluminosos.
- No hay historial de compilación para arreglos privados.
Era necesario ocuparse de estas tareas y agregar pequeñas cosas agradables que los desarrolladores no rechazarían.
¿Qué son arreglos privados?
Una solución privada en el contexto de nuestro desarrollo es un cierto conjunto de correcciones en el código, que se almacena en la plataforma del Team Foundation Server para la rama de lanzamiento. Una pequeña aclaración para aquellos que no están demasiado familiarizados con la terminología de TFS:
- check-in: un conjunto de cambios locales en el código fuente, que se realiza en el código almacenado en TFS. Esta verificación puede verificarse mediante los procesos de Integración continua / Verificación cerrada que le permiten omitir solo el código correcto y rechazar todas las verificaciones que violen la recopilación del proyecto final.
- shelveset: un conjunto de cambios locales en el código fuente que no se realiza directamente en el código fuente ubicado en TFS, pero que es accesible por su nombre. El shellset se puede implementar en la máquina local del desarrollador o sistema de compilación para trabajar con código modificado que no está incluido en TFS. Además, el shellset se puede agregar a TFS como una comprobación después de la implementación, cuando se completa todo el trabajo con él. Por ejemplo, el verificador de puerta funciona de esta manera. Primero, se marca el shellset en el constructor. Si la verificación es exitosa, el shellset se convierte en una verificación
Esto es lo que hace el constructor de arreglos privados:
- Obtiene el nombre (número) del conjunto de shell y lo implementa en el generador de arreglos privados. Como resultado, obtenemos el código fuente del producto de lanzamiento más los cambios / arreglos del shellset. La rama de liberación permanece sin cambios.
- En un generador de arreglos privados, se va a ejecutar un proyecto o una serie de proyectos para los que se ha realizado un arreglo privado.
- El conjunto de archivos binarios compilados se copia en el directorio de red del arreglo privado. El catálogo contiene el nombre del shellset, que es una secuencia de números.
- El código fuente en el generador de arreglos privados se restaura a su forma original.
Para comodidad de los desarrolladores, se utiliza una interfaz web donde puede especificar el producto para el que desea recopilar un arreglo privado, especificar el número de shellset, seleccionar los proyectos para los que desea recopilar un arreglo privado y agregar el conjunto de arreglos a la cola. La captura de pantalla siguiente muestra la versión final de trabajo de la aplicación web, que muestra el estado actual de la compilación, la cola de arreglos privados y el historial de su ensamblaje. En nuestro ejemplo, solo se considera la cola para ensamblar arreglos privados.
Lo que era mio
- Un generador de arreglos privados que recopiló arreglos privados del conjunto de shell TFS al iniciar una aplicación de consola con los parámetros de línea de comandos dados.
- Veeam.Builder.Agent: un servicio WCF escrito por Veeam que inicia la aplicación con parámetros en modo consola bajo el usuario actual y devuelve el estado actual de la aplicación.
- El servicio web IIS es una aplicación en formularios Windows Forms que le permite ingresar el nombre del shellset, los parámetros especificados y comenzar el proceso de creación de una solución privada.
- Un conocimiento de programación muy superficial es C ++, un poco de C # en la universidad, y escribe pequeñas aplicaciones para la automatización, agregando nuevas funciones a los procesos de construcción actuales y como un pasatiempo.
- Los colegas con experiencia, Google y los artículos indios de MSDN son fuentes de respuestas a todas las preguntas.
Que haremos
En este artículo contaré cómo implementé la puesta en cola del ensamblaje de arreglos y su lanzamiento secuencial en el generador. Aquí están las partes de la solución:
- QBuilder.AppQueue es mi servicio WCF que proporciona trabajo con la cola de compilación y llama al servicio Veeam.Builder.Agent para ejecutar el programa de compilación.
- dummybuild.exe es un programa de código auxiliar utilizado para la depuración y como una ayuda visual. Necesario para visualizar los parámetros transferidos.
- QBuilder.AppLauncher: servicio WCF que ejecuta aplicaciones en la consola del usuario actual y funciona en modo interactivo. Este es un análogo significativamente simplificado del programa Veeam.Builder.Agent escrito específicamente para este artículo. El servicio original puede funcionar como un servicio de Windows y ejecutar aplicaciones en la consola, lo que requiere un trabajo adicional con la API de Windows. Para describir todos los trucos, se requeriría un artículo separado. Mi ejemplo funciona como un simple servicio de consola interactiva y usa dos funciones: iniciar un proceso con parámetros y verificar su estado.
Además, creamos una nueva aplicación web conveniente que puede trabajar con varios constructores y mantener registros de eventos. Para no sobrecargar el artículo, tampoco hablaremos de él en detalle. Además, este artículo no describe el trabajo con TFS, con el historial de almacenamiento de arreglos privados recopilados y varias clases y funciones auxiliares.

Crear servicios WCF
Hay muchos artículos detallados que describen la creación de servicios WCF. Me gustó sobre todo el
material del sitio de Microsoft . Lo tomé como base para el desarrollo. Para facilitar mi conocimiento del proyecto, también presenté los
binarios . ¡Empecemos!
Cree el servicio QBuilder.AppLauncher
Aquí solo tendremos el disco primario del servicio. En esta etapa, debemos asegurarnos de que el servicio se inicie y funcione. Además, el código es idéntico para QBuilder.AppLauncher y QBuilder.AppQueue, por lo que este proceso deberá repetirse dos veces.
- Cree una nueva aplicación de consola llamada QBuilder.AppLauncher
- Cambiar el nombre de Program.cs a Service.cs
- Cambiar el nombre del espacio de nombres a QBuilder.AppLauncher
- Agregue las siguientes referencias al proyecto:
a. System.ServiceModel.dll
b. System.ServiceProcess.dll
c. System.Configuration.Install.dll
- Agregue las siguientes definiciones a Service.cs
using System.ComponentModel; using System.ServiceModel; using System.ServiceProcess; using System.Configuration; using System.Configuration.Install;
Durante el montaje posterior, también se necesitarán las siguientes definiciones:
using System.Reflection; using System.Xml.Linq; using System.Xml.XPath;
- Definimos la interfaz IAppLauncher y agregamos funciones para trabajar con la cola:
- En la clase AppLauncherService, implementamos la interfaz y la función de prueba TestConnection:
public class AppLauncherService : IAppLauncher { public bool TestConnection() { return true; } }
- Cree una nueva clase AppLauncherWindowsService que herede la clase ServiceBase. Agregue la variable local serviceHost, un enlace a ServiceHost. Definimos el método Main, que llama a ServiceBase.Run (nuevo AppLauncherWindowsService ()):
public class AppLauncherWindowsService : ServiceBase { public ServiceHost serviceHost = null; public AppLauncherWindowsService() {
- Anule la función OnStart () que crea la nueva instancia de ServiceHost:
protected override void OnStart(string[] args) { if (serviceHost != null) { serviceHost.Close(); }
- Anule la función onStop que cierra la instancia de ServiceHost:
protected override void OnStop() { if (serviceHost != null) { serviceHost.Close(); serviceHost = null; } } }
- Cree una nueva clase ProjectInstaller, heredada del instalador y marcada con RunInstallerAttribute, que se establece en True. Esto le permite instalar el servicio de Windows utilizando el programa installutil.exe:
[RunInstaller(true)] public class ProjectInstaller : Installer { private ServiceProcessInstaller process; private ServiceInstaller service; public ProjectInstaller() { process = new ServiceProcessInstaller(); process.Account = ServiceAccount.LocalSystem; service = new ServiceInstaller(); service.ServiceName = "QBuilder App Launcher"; Installers.Add(process); Installers.Add(service); } }
- Cambie el contenido del archivo app.config:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <services> <service name="QBuilder.AppLauncher.AppLauncherService" behaviorConfiguration="AppLauncherServiceBehavior"> <host> <baseAddresses> <add baseAddress="http://localhost:8000/QBuilderAppLauncher/service"/> </baseAddresses> </host> <endpoint address="" binding="wsHttpBinding" contract="QBuilder.AppLauncher.IAppLauncher" /> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /> </service> </services> <behaviors> <serviceBehaviors> <behavior name="AppLauncherServiceBehavior"> <serviceMetadata httpGetEnabled="true"/> <serviceDebug includeExceptionDetailInFaults="False"/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration>
Comprobación de la capacidad de servicio del servicio.
- Recopilamos el servicio.
- Instálelo con el comando installutil.exe
1) Vaya a la carpeta donde se encuentra el archivo de servicio compilado
2) Ejecute el comando de instalación:
C: \ Windows \ Microsoft.NET \ Framework64 \ v4.0.30319 \ InstallUtil.exe - Entramos en el complemento services.msc, verificamos la disponibilidad del servicio QBuilder App Launcher y lo ejecutamos.
- Verificamos la capacidad de servicio del servicio utilizando el programa WcfTestClient.exe, que se incluye con VisualStudio:
1) Ejecute WcfTestClient
2) Agregue la dirección del servicio: http: // localhost: 8000 / QBuilderAppLauncher / service
3) Se abre la interfaz de servicio:

4) Llamamos a la función de prueba TestConnection, verificamos que todo funcione y la función devuelve un valor:

Ahora que tenemos un disco funcional del servicio, agregamos las funciones que necesitamos.
¿Por qué necesito una función de prueba que no hace nada?
Cuando comencé a aprender a escribir un servicio WCF desde cero, leí un montón de artículos sobre este tema. Sobre la mesa tenía una docena o dos hojas impresas en las que podía averiguar qué y cómo. Admito que no logré iniciar el servicio de inmediato. Pasé mucho tiempo y llegué a la conclusión de que es realmente importante hacer un disco de servicio. Con él estarás seguro de que todo funciona y podrás comenzar a implementar las funciones necesarias. El enfoque puede parecer un desperdicio, pero facilitará la vida si un montón de código escrito no funciona como debería.
Agregue la capacidad de ejecutar desde la consola
De vuelta a la aplicación. En la etapa de depuración y en otros casos, es necesario iniciar el servicio en forma de una aplicación de consola sin registrarse como servicio. Esta es una característica muy útil que le permite prescindir del uso tedioso de depuradores. Es en este modo que funciona el servicio QBuilder.AppLauncher. Aquí se explica cómo implementarlo:
- Agregue el procedimiento RunInteractive a la clase AppLauncherWindowsService, que proporciona el servicio en modo consola:
static void RunInteractive(ServiceBase[] services) { Console.WriteLine("Service is running in interactive mode."); Console.WriteLine(); var start = typeof(ServiceBase).GetMethod("OnStart", BindingFlags.Instance | BindingFlags.NonPublic); foreach (var service in services) { Console.Write("Starting {0}...", service.ServiceName); start.Invoke(service, new object[] { new string[] { } }); Console.Write("Started {0}", service.ServiceName); } Console.WriteLine(); Console.WriteLine("Press any key to stop the services and end the process..."); Console.ReadKey(); Console.WriteLine(); var stop = typeof(ServiceBase).GetMethod("OnStop", BindingFlags.Instance | BindingFlags.NonPublic); foreach (var service in services) { Console.Write("Stopping {0}...", service.ServiceName); stop.Invoke(service, null); Console.WriteLine("Stopped {0}", service.ServiceName); } Console.WriteLine("All services stopped."); }
- Realizamos cambios en el procedimiento Principal: agregue el procesamiento de los parámetros de la línea de comandos. Con la opción / console y una sesión de usuario activa abierta, lanzamos el programa en modo interactivo. De lo contrario, lo lanzamos como un servicio.
public static void Main(string[] args) { var services = new ServiceBase[] { new AppLauncherWindowsService() };
Agregue funciones para iniciar la aplicación y verificar su estado
El servicio se hace extremadamente simple, no hay controles adicionales. Solo puede ejecutar aplicaciones en la versión de consola y en nombre del administrador. También puede iniciarlos como un servicio, pero no los verá, girarán en segundo plano y solo podrá verlos a través del Administrador de tareas. Todo esto se puede implementar, pero este es un tema para un artículo separado. Lo principal para nosotros aquí es un claro ejemplo de trabajo.
- Primero, agregue la variable global appProcess, que almacena el proceso actualmente en ejecución.
public class AppLauncherService : IAppLauncher
a la public class AppLauncherService : IAppLauncher
:
public class AppLauncherService : IAppLauncher { Process appProcess;
- Agregue una función a la misma clase que verifica el estado del proceso en ejecución:
public bool IsStarted() { if (appProcess!=null) { if (appProcess.HasExited) { return false; } else { return true; } } else { return false; } }
La función devuelve falso si el proceso no existe o no se está ejecutando, y verdadero si el proceso está activo.
- Agregue la función para iniciar la aplicación:
public bool Start(string fileName, string arguments, string workingDirectory, string domain, string userName, int timeoutInMinutes) { ProcessStartInfo processStartInfo = new ProcessStartInfo(); processStartInfo.FileName = fileName; processStartInfo.Arguments = arguments; processStartInfo.Domain = domain; processStartInfo.UserName = userName; processStartInfo.CreateNoWindow = false; processStartInfo.UseShellExecute = false; try { if (appProcess!=null) { if (!appProcess.HasExited) { Console.WriteLine("Process is still running. Waiting..."); return false; } } } catch (Exception ex) { Console.WriteLine("Error while checking process: {0}", ex); } try { appProcess = new Process(); appProcess.StartInfo = processStartInfo; appProcess.Start(); } catch (Exception ex) { Console.WriteLine("Error while starting process: {0}",ex); } return true; }
La función inicia cualquier aplicación con parámetros. Los parámetros de dominio y nombre de usuario no se utilizan en este contexto y pueden estar vacíos, ya que el servicio inicia la aplicación desde la sesión de la consola con derechos de administrador.
Lanzamiento del servicio QBuilder.AppLauncher
Como se describió anteriormente, este servicio funciona de manera interactiva y le permite ejecutar aplicaciones en la sesión actual del usuario, verifica si el proceso se está ejecutando o si ya se completó.
- Para trabajar, necesita los archivos QBuilder.AppLauncher.exe y QBuilder.AppLauncher.exe.config, que se encuentran en el archivo en el enlace de arriba. El código fuente de esta aplicación para el autoensamblaje también se encuentra allí.
- Iniciamos el servicio con derechos de administrador.
- Se abrirá la ventana de la consola del servicio:

Cualquier pulsación en la consola de servicio la cierra, tenga cuidado.
- Para las pruebas, ejecute wcftestclient.exe, que se incluye con Visual Studio. Verificamos la disponibilidad del servicio en http: // localhost: 8000 / QBuilderAppLauncher / service o abrimos el enlace en Internet Explorer.
Si todo funciona, vaya al siguiente paso.
Crear el servicio QBuilder.AppQueue
¡Y ahora pasemos al servicio más importante, para el que se escribió todo el artículo! Repetimos la secuencia de acciones en el capítulo "Creación del servicio QBuilder.AppLauncher" y en el capítulo "Agregar el lanzamiento desde la consola", reemplazando AppLauncher con AppQueue en el código.
Agregue un enlace al servicio QBuilder.AppLauncher para usar en el servicio de cola
- En el Explorador de soluciones para nuestro proyecto, seleccione Agregar referencia de servicio y especifique la dirección: localhost : 8000 / QBuilderAppLauncher / service
- Seleccione el espacio de nombres: AppLauncherService.
Ahora podemos acceder a la interfaz de servicio desde nuestro programa.
Crear una estructura para almacenar elementos de cola
En el espacio de nombres de QBuilder.AppQueue, agregue la clase QBuildRecord:
Implementación de la clase de cola CXmlQueue
Agregamos la clase CXmlQueue.cs a nuestro proyecto, donde se ubicarán varios procedimientos para trabajar con el archivo XML:
- Constructor CXmlQueue: establece el nombre inicial del archivo donde se almacena la cola.
- SetCurrentBuild: escribe información sobre la compilación actual en el archivo XML de la cola. Este es un elemento que no está en la cola; almacena información sobre el proceso actualmente en ejecución. Puede estar vacio
- GetCurrentBuild: obtiene los parámetros del proceso en ejecución del archivo XML de cola. Puede estar vacio
- ClearCurrentBuild: borra el elemento de construcción actual en el archivo XML de cola si el proceso finaliza.
- OpenXmlQueue: función para abrir el archivo XML donde se almacena la cola. Si falta el archivo, se crea uno nuevo.
- GetLastQueueBuildNumber: cada compilación en la cola tiene su propio número de serie único. Esta función devuelve su valor, que se almacena en el atributo raíz.
- IncrementLastQueueBuildNumber: aumenta el valor del número de compilación al poner en cola una nueva compilación.
- GetCurrentQueue: devuelve una lista de elementos QBuildRecord del archivo XML de la cola.
En el código original, todos estos procedimientos se colocaron en la clase principal, pero para mayor claridad, hice una clase separada CXmlQueue. La clase se crea en el espacio de nombres QBuilder.AppQueue, verifique que se hayan especificado todas las definiciones necesarias:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml.Linq; using System.Xml.XPath; using System.IO; namespace QBuilder.AppQueue { . . . }
Entonces, estamos implementando. La clase CXmlQueue en sí misma:
Haga clic para expandir el spoiler con código La cola en el archivo XML es la siguiente:
<?xml version="1.0" encoding="utf-8"?> <BuildsQueue BuildNumber="23"> <build BuildId="14" IssueId="26086" IssueName="TestIssueName" StartDate="2018-06-13T16:49:50.515238+02:00" Build_CSharp="true" Build_Cpp="true" /> <build BuildId="15" IssueId="59559" IssueName="TestIssueName" StartDate="2018-06-13T16:49:50.6880927+02:00" Build_CSharp="true" Build_Cpp="true" /> <build BuildId="16" IssueId="45275" IssueName="TestIssueName" StartDate="2018-06-13T16:49:50.859937+02:00" Build_CSharp="true" Build_Cpp="true" /> <build BuildId="17" IssueId="30990" IssueName="TestIssueName" StartDate="2018-06-13T16:49:51.0321322+02:00" Build_CSharp="true" Build_Cpp="true" /> <build BuildId="18" IssueId="16706" IssueName="TestIssueName" StartDate="2018-06-13T16:49:51.2009904+02:00" Build_CSharp="true" Build_Cpp="true" /> <build BuildId="19" IssueId="66540" IssueName="TestIssueName" StartDate="2018-06-13T16:49:51.3581274+02:00" Build_CSharp="true" Build_Cpp="true" /> <build BuildId="20" IssueId="68618" IssueName="TestIssueName" StartDate="2018-06-13T16:49:51.5087854+02:00" Build_CSharp="true" Build_Cpp="true" /> <build BuildId="21" IssueId="18453" IssueName="TestIssueName" StartDate="2018-06-13T16:49:51.6713477+02:00" Build_CSharp="true" Build_Cpp="true" /> <build BuildId="22" IssueId="68288" IssueName="TestIssueName" StartDate="2018-06-13T16:49:51.8277942+02:00" Build_CSharp="true" Build_Cpp="true" /> <build BuildId="23" IssueId="89884" IssueName="TestIssueName" StartDate="2018-06-13T16:49:52.0151294+02:00" Build_CSharp="true" Build_Cpp="true" /> <currentbuild BuildId="13" IssueId="4491" StartDate="13.06.2018 16:53:16" /> </BuildsQueue>
Cree un archivo BuildQueue.xml con este contenido y póngalo en el directorio con el archivo ejecutable. Este archivo se utilizará en la depuración de prueba para que coincida con los resultados de la prueba.
Agregar clase de funciones auxiliares
En esta clase, coloco funciones de ayuda. En este momento solo hay una función, FormatParameters, que realiza el formateo de los parámetros para pasarlos a la aplicación de consola para su lanzamiento. Listado del archivo AuxFunctions.cs:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace QBuilder.AppQueue { class AuxFunctions {
Agregar nuevas funciones a la interfaz de servicio
La función de prueba TestConnection se puede eliminar en esta etapa. Para implementar el trabajo de la cola, necesitaba el siguiente conjunto de funciones:
- PushBuild (QBuildRecord): nulo. Esta es una función que agrega un nuevo valor al archivo XML de cola con los parámetros QBuildRecord
- TestPushBuild (): nulo. Esta es una función de prueba que agrega datos de prueba a una cola en un archivo XML.
- PullBuild: QBuildRecord. Esta es una función que recupera el valor QBuildRecord del archivo XML de la cola. Puede estar vacio.
La interfaz será así:
public interface IAppQueue {
Implementamos funciones en la clase AppQueueService: IAppQueue:
Haga clic para expandir el spoiler con código public class AppQueueService : IAppQueue {
Realizar cambios en AppQueueWindowsService: clase ServiceBase
Agregue nuevas variables al cuerpo de la clase:
En el constructor AppQueueWindowsService (), agregue funciones para leer el archivo de configuración, inicializar servicios y clases de cola:
AgentTimeout: frecuencia de respuesta del temporizador. Indicado en milisegundos. Aquí establecemos que el temporizador debe disparar cada 30 segundos. En el original, este parámetro está en el archivo de configuración. Para el artículo, decidí establecerlo en código.
Agregue la función para verificar el proceso de compilación en ejecución a la clase:
Agregue el procedimiento para trabajar con el temporizador:
private void TimerTick(object sender, System.Timers.ElapsedEventArgs e) { try {
OnStart, :
:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; using System.ServiceModel; using System.ServiceProcess; using System.Configuration; using System.Configuration.Install; using System.Reflection; using System.Xml.Linq; using System.Xml.XPath; using QBuilder.AppQueue.AppLauncherService;
App.config
:
<appSettings> <add key="QueueFileName" value="BuildQueue.xml"/> <add key="BuildToolPath" value="c:\temp\dummybuild.exe"/> <add key="BuildToolWorkDir" value="c:\temp\"/> <add key="LocalDomain" value="."/> <add key="UserName" value="username"/> <add key="ScriptPath" value="C:\Temp\BuildSample.bld"/> </appSettings>
- QBuilder.AppLauncher.zip. .
- dummybuild.exe binaries , , c:\temp. , . , BuildToolPath BuildToolWorkDir .
- \QBuilder.AppLauncher\binaries\QBuilder.AppLauncher\ QBuilder.AppLauncher.exe . .
- QBuilder.AppQueue.exe /console .
- , :

- . , 30 :

- BuildQueue.xml , currentbuild:
<?xml version="1.0" encoding="utf-8"?> <BuildsQueue BuildNumber="23"> <build BuildId="19" IssueId="66540" IssueName="TestIssueName" StartDate="2018-06-13T16:49:51.3581274+02:00" Build_CSharp="true" Build_Cpp="true" /> <build BuildId="20" IssueId="68618" IssueName="TestIssueName" StartDate="2018-06-13T16:49:51.5087854+02:00" Build_CSharp="true" Build_Cpp="true" /> <build BuildId="21" IssueId="18453" IssueName="TestIssueName" StartDate="2018-06-13T16:49:51.6713477+02:00" Build_CSharp="true" Build_Cpp="true" /> <build BuildId="22" IssueId="68288" IssueName="TestIssueName" StartDate="2018-06-13T16:49:51.8277942+02:00" Build_CSharp="true" Build_Cpp="true" /> <build BuildId="23" IssueId="89884" IssueName="TestIssueName" StartDate="2018-06-13T16:49:52.0151294+02:00" Build_CSharp="true" Build_Cpp="true" /> <currentbuild BuildId="18" IssueId="16706" StartDate="13.06.2018 23:20:06" /> </BuildsQueue>
- dummy , :
<?xml version="1.0" encoding="utf-8"?> <BuildsQueue BuildNumber="23"> <build BuildId="21" IssueId="18453" IssueName="TestIssueName" StartDate="2018-06-13T16:49:51.6713477+02:00" Build_CSharp="true" Build_Cpp="true" /> <build BuildId="22" IssueId="68288" IssueName="TestIssueName" StartDate="2018-06-13T16:49:51.8277942+02:00" Build_CSharp="true" Build_Cpp="true" /> <build BuildId="23" IssueId="89884" IssueName="TestIssueName" StartDate="2018-06-13T16:49:52.0151294+02:00" Build_CSharp="true" Build_Cpp="true" /> <currentbuild BuildId="20" IssueId="68618" StartDate="13.06.2018 23:24:25" /> </BuildsQueue>
!
Resultados
powershell- . C#. rulesets — , setup-. — , . — MD5- -, .
,, — . , , . - .
, XML, , . , . , , .
, WCF-, XML-. :
PS , . , , .